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