-
Notifications
You must be signed in to change notification settings - Fork 786
/
gitops.go
257 lines (228 loc) · 8.26 KB
/
gitops.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
package apps
import (
"fmt"
"os"
"path/filepath"
"github.com/jenkins-x/jx/v2/pkg/gits"
"github.com/jenkins-x/jx/v2/pkg/helm"
"github.com/jenkins-x/jx/v2/pkg/util"
"k8s.io/helm/pkg/proto/hapi/chart"
"github.com/ghodss/yaml"
v1 "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io/v1"
"io/ioutil"
"github.com/jenkins-x/jx-logging/pkg/log"
"github.com/jenkins-x/jx/v2/pkg/environments"
"github.com/pkg/errors"
)
// GitOpsOptions is the options used for Git Operations for apps
type GitOpsOptions struct {
*InstallOptions
}
// AddApp adds the app with version rooted in dir from the repository. An alias can be specified.
func (o *GitOpsOptions) AddApp(app string, dir string, version string, repository string, alias string, autoMerge bool) error {
details := gits.PullRequestDetails{
BranchName: "add-app-" + app + "-" + version,
Title: fmt.Sprintf("Add %s %s", app, version),
Message: fmt.Sprintf("Add app %s %s", app, version),
}
options := environments.EnvironmentPullRequestOptions{
Gitter: o.Gitter,
ModifyChartFn: environments.CreateAddRequirementFn(app, alias, version,
repository, o.valuesFiles, dir, o.Verbose, o.Helmer),
GitProvider: o.GitProvider,
}
info, err := options.Create(o.DevEnv, o.EnvironmentCloneDir, &details, nil, "", autoMerge)
if err != nil {
return errors.Wrapf(err, "creating pr for %s", app)
}
if info != nil {
log.Logger().Infof("Added app via Pull Request %s", info.PullRequest.URL)
} else {
log.Logger().Infof("Already up to date")
}
return nil
}
// UpgradeApp upgrades the app (or all apps if empty) to a version (
// or latest if empty) from a repository with username and password.
// If one app is being upgraded an alias can be specified.
func (o *GitOpsOptions) UpgradeApp(app string, version string, repository string, username string, password string,
alias string, interrogateChartFunc func(dir string, existing map[string]interface{}) (*ChartDetails,
error), autoMerge bool) error {
all := true
details := gits.PullRequestDetails{}
// use a random string in the branch name to ensure we use a unique git branch and fail to push
rand, err := util.RandStringBytesMaskImprSrc(5)
if err != nil {
return errors.Wrapf(err, "failed to generate a random string")
}
if app != "" {
all = false
versionBranchName := version
if versionBranchName == "" {
versionBranchName = "latest"
}
details.BranchName = fmt.Sprintf("upgrade-app-%s-%s-%s", app, versionBranchName, rand)
} else {
details.BranchName = fmt.Sprintf("upgrade-all-apps-%s", rand)
details.Title = fmt.Sprintf("Upgrade all apps")
details.Message = fmt.Sprintf("Upgrade all apps:\n")
}
var interrogateCleanup func()
defer func() {
if interrogateCleanup != nil {
interrogateCleanup()
}
}()
inspectChartFunc := func(chartDir string, values map[string]interface{}) error {
chartDetails, err := interrogateChartFunc(chartDir, values)
interrogateCleanup = chartDetails.Cleanup
if err != nil {
return errors.Wrapf(err, "asking questions for %s", chartDir)
}
return nil
}
options := environments.EnvironmentPullRequestOptions{
Gitter: o.Gitter,
ModifyChartFn: environments.CreateUpgradeRequirementsFn(all, app, alias, version, username, password,
o.Helmer, inspectChartFunc, o.Verbose, o.valuesFiles),
GitProvider: o.GitProvider,
}
_, err = options.Create(o.DevEnv, o.EnvironmentCloneDir, &details, nil, app, autoMerge)
if err != nil {
return err
}
return nil
}
// DeleteApp deletes the app with alias
func (o *GitOpsOptions) DeleteApp(app string, alias string, autoMerge bool) error {
modifyChartFn := func(requirements *helm.Requirements, metadata *chart.Metadata, values map[string]interface{},
templates map[string]string, dir string, details *gits.PullRequestDetails) error {
// See if the app already exists in requirements
found := false
for i, d := range requirements.Dependencies {
if d.Name == app && d.Alias == alias {
found = true
requirements.Dependencies = append(requirements.Dependencies[:i], requirements.Dependencies[i+1:]...)
}
}
// If app not found, add it
if !found {
a := app
if alias != "" {
a = fmt.Sprintf("%s with alias %s", a, alias)
}
return fmt.Errorf("unable to delete app %s as not installed", app)
}
if info, err := os.Stat(filepath.Join(dir, app)); err == nil {
if info.IsDir() {
err := util.DeleteFile(info.Name())
if err != nil {
return err
}
} else {
log.Logger().Warnf("Not removing %s for %s because it is not a directory", info.Name(), app)
}
}
return nil
}
details := gits.PullRequestDetails{
BranchName: "delete-app-" + app,
Title: fmt.Sprintf("Delete %s", app),
Message: fmt.Sprintf("Delete app %s", app),
}
options := environments.EnvironmentPullRequestOptions{
Gitter: o.Gitter,
ModifyChartFn: modifyChartFn,
GitProvider: o.GitProvider,
}
info, err := options.Create(o.DevEnv, o.EnvironmentCloneDir, &details, nil, "", autoMerge)
if err != nil {
return err
}
log.Logger().Infof("Delete app via Pull Request %s", info.PullRequest.URL)
return nil
}
// GetApps retrieves all the apps information for the given appNames from the repository and / or the CRD API
func (o *GitOpsOptions) GetApps(appNames map[string]bool, expandFn func([]string) (*v1.AppList, error)) (*v1.AppList, error) {
// AddApp, DeleteApp, and UpgradeApps delegate selecting/creating the directory to clone in to environments/gitops.go's
// Create function, but here we need to create the directory explicitly. since we aren't calling Create, because we're
// not creating a pull request.
dir, err := ioutil.TempDir("", "get-apps-")
if err != nil {
return nil, err
}
defer os.RemoveAll(dir)
gitInfo, err := gits.ParseGitURL(o.DevEnv.Spec.Source.URL)
if err != nil {
return nil, errors.Wrapf(err, "parsing dev env repo URL %s", o.DevEnv.Spec.Source.URL)
}
providerInfo, err := o.GitProvider.GetRepository(gitInfo.Organisation, gitInfo.Name)
if err != nil {
return nil, errors.Wrapf(err, "determining git provider information for %s", o.DevEnv.Spec.Source.URL)
}
cloneUrl := providerInfo.CloneURL
userDetails := o.GitProvider.UserAuth()
originFetchURL, err := o.Gitter.CreateAuthenticatedURL(cloneUrl, &userDetails)
if err != nil {
return nil, errors.Wrapf(err, "failed to create authenticated fetch URL for %s", cloneUrl)
}
err = o.Gitter.Clone(originFetchURL, dir)
if err != nil {
return nil, errors.Wrapf(err, "failed to clone %s to dir %s", cloneUrl, dir)
}
err = o.Gitter.Checkout(dir, o.DevEnv.Spec.Source.Ref)
if err != nil {
return nil, errors.Wrapf(err, "failed to checkout %s to dir %s", o.DevEnv.Spec.Source.Ref, dir)
}
envDir := filepath.Join(dir, helm.DefaultEnvironmentChartDir)
if err != nil {
return nil, err
}
exists, err := util.DirExists(envDir)
if err != nil {
return nil, err
}
if !exists {
envDir = dir
}
requirementsFile, err := ioutil.ReadFile(filepath.Join(envDir, helm.RequirementsFileName))
if err != nil {
return nil, errors.Wrap(err, "couldn't read the environment's requirements.yaml file")
}
reqs := helm.Requirements{}
err = yaml.Unmarshal(requirementsFile, &reqs)
if err != nil {
return nil, errors.Wrap(err, "couldn't unmarshal the environment's requirements.yaml file")
}
appsList := v1.AppList{}
for _, d := range reqs.Dependencies {
if appNames[d.Name] == true || len(appNames) == 0 {
//Make sure we ignore the jenkins-x-platform requirement
if d.Name != "jenkins-x-platform" {
resourcesInCRD, _ := expandFn([]string{d.Name})
if len(resourcesInCRD.Items) != 0 {
appsList.Items = append(appsList.Items, resourcesInCRD.Items...)
} else {
appPath := filepath.Join(envDir, d.Name, "templates", "app.yaml")
exists, err := util.FileExists(appPath)
if err != nil {
return nil, errors.Wrapf(err, "there was a problem checking if %s exists", appPath)
}
if exists {
appFile, err := ioutil.ReadFile(appPath)
if err != nil {
return nil, errors.Wrapf(err, "there was a problem reading the app.yaml file of %s", d.Name)
}
app := v1.App{}
err = yaml.Unmarshal(appFile, &app)
if err != nil {
return nil, errors.Wrapf(err, "there was a problem unmarshalling the app.yaml file of %s", d.Name)
}
appsList.Items = append(appsList.Items, app)
}
}
}
}
}
return &appsList, nil
}