github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/pipelinescheduler/generator.go (about) 1 package pipelinescheduler 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "sort" 9 "strings" 10 "time" 11 12 "github.com/google/go-cmp/cmp" 13 "github.com/google/go-cmp/cmp/cmpopts" 14 "github.com/jenkins-x/jx-logging/pkg/log" 15 "github.com/olli-ai/jx/v2/pkg/kube" 16 "github.com/olli-ai/jx/v2/pkg/kube/naming" 17 "github.com/olli-ai/jx/v2/pkg/prow" 18 "github.com/jenkins-x/lighthouse/pkg/config/branchprotection" 19 "github.com/jenkins-x/lighthouse/pkg/config/job" 20 "github.com/jenkins-x/lighthouse/pkg/config/keeper" 21 22 "github.com/google/uuid" 23 "github.com/olli-ai/jx/v2/pkg/environments" 24 "github.com/olli-ai/jx/v2/pkg/gits" 25 "github.com/olli-ai/jx/v2/pkg/helm" 26 "github.com/jenkins-x/lighthouse/pkg/config" 27 "github.com/jenkins-x/lighthouse/pkg/plugins" 28 v1 "k8s.io/api/core/v1" 29 kubeerrors "k8s.io/apimachinery/pkg/api/errors" 30 "k8s.io/helm/pkg/proto/hapi/chart" 31 32 "k8s.io/client-go/kubernetes" 33 34 "github.com/ghodss/yaml" 35 36 jenkinsv1 "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io/v1" 37 "github.com/jenkins-x/jx-api/pkg/client/clientset/versioned" 38 "github.com/pkg/errors" 39 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 40 ) 41 42 // GenerateProw will generate the prow config for the namespace 43 func GenerateProw(gitOps bool, autoApplyConfigUpdater bool, jxClient versioned.Interface, namespace string, teamSchedulerName string, devEnv *jenkinsv1.Environment, loadSchedulerResourcesFunc func(versioned.Interface, string) (map[string]*jenkinsv1.Scheduler, *jenkinsv1.SourceRepositoryGroupList, *jenkinsv1.SourceRepositoryList, error)) (*config.Config, 44 *plugins.Configuration, error) { 45 if loadSchedulerResourcesFunc == nil { 46 loadSchedulerResourcesFunc = loadSchedulerResources 47 } 48 schedulers, sourceRepoGroups, sourceRepos, err := loadSchedulerResourcesFunc(jxClient, namespace) 49 if err != nil { 50 return nil, nil, errors.Wrapf(err, "loading scheduler resources") 51 } 52 if sourceRepos == nil || len(sourceRepos.Items) < 1 { 53 return nil, nil, errors.New("No source repository resources were found") 54 } 55 defaultScheduler := schedulers[teamSchedulerName] 56 leaves := make([]*SchedulerLeaf, 0) 57 for _, sourceRepo := range sourceRepos.Items { 58 applicableSchedulers := []*jenkinsv1.SchedulerSpec{} 59 // Apply config-updater to devEnv 60 applicableSchedulers = addConfigUpdaterToDevEnv(gitOps, autoApplyConfigUpdater, applicableSchedulers, devEnv, &sourceRepo.Spec) 61 // Apply repo scheduler 62 applicableSchedulers = addRepositoryScheduler(sourceRepo, schedulers, applicableSchedulers) 63 // Apply project schedulers 64 applicableSchedulers = addProjectSchedulers(sourceRepoGroups, sourceRepo, schedulers, applicableSchedulers) 65 // Apply team scheduler 66 applicableSchedulers = addTeamScheduler(teamSchedulerName, defaultScheduler, applicableSchedulers) 67 if len(applicableSchedulers) < 1 { 68 continue 69 } 70 merged, err := Build(applicableSchedulers) 71 if err != nil { 72 return nil, nil, errors.Wrapf(err, "building scheduler") 73 } 74 leaves = append(leaves, &SchedulerLeaf{ 75 Repo: sourceRepo.Spec.Repo, 76 Org: sourceRepo.Spec.Org, 77 SchedulerSpec: merged, 78 }) 79 if err != nil { 80 return nil, nil, errors.Wrapf(err, "building prow config") 81 } 82 } 83 cfg, plugs, err := BuildProwConfig(leaves) 84 if err != nil { 85 return nil, nil, errors.Wrapf(err, "building prow config") 86 } 87 if cfg != nil { 88 cfg.PodNamespace = namespace 89 cfg.LighthouseJobNamespace = namespace 90 } 91 return cfg, plugs, nil 92 } 93 94 func loadSchedulerResources(jxClient versioned.Interface, namespace string) (map[string]*jenkinsv1.Scheduler, *jenkinsv1.SourceRepositoryGroupList, *jenkinsv1.SourceRepositoryList, error) { 95 schedulers, err := jxClient.JenkinsV1().Schedulers(namespace).List(metav1.ListOptions{}) 96 if err != nil { 97 return nil, nil, nil, errors.WithStack(err) 98 } 99 if len(schedulers.Items) == 0 { 100 return nil, nil, nil, errors.New("No pipeline schedulers are configured") 101 } 102 lookup := make(map[string]*jenkinsv1.Scheduler) 103 for _, item := range schedulers.Items { 104 lookup[item.Name] = item.DeepCopy() 105 } 106 // Process Schedulers linked to SourceRepositoryGroups 107 sourceRepoGroups, err := jxClient.JenkinsV1().SourceRepositoryGroups(namespace).List(metav1.ListOptions{}) 108 if err != nil { 109 return nil, nil, nil, errors.Wrapf(err, "Error finding source repository groups") 110 } 111 // Process Schedulers linked to SourceRepositoryGroups 112 sourceRepos, err := jxClient.JenkinsV1().SourceRepositories(namespace).List(metav1.ListOptions{}) 113 if err != nil { 114 return nil, nil, nil, errors.Wrapf(err, "Error finding source repositories") 115 } 116 return lookup, sourceRepoGroups, sourceRepos, nil 117 } 118 119 // CreateSchedulersFromProwConfig will generate Pipeline Schedulers from the prow configmaps in the specified namespace or the config and plugins files specified as an option 120 func CreateSchedulersFromProwConfig(configFileLocation string, pluginsFileLocation string, skipVerification bool, dryRun bool, gitOps bool, jxClient versioned.Interface, kubeClient kubernetes.Interface, namespace string, teamSchedulerName string, devEnv *jenkinsv1.Environment) ([]*jenkinsv1.SourceRepositoryGroup, []*jenkinsv1.SourceRepository, map[string]*jenkinsv1.Scheduler, error) { 121 prowConfig, pluginConfig, err := loadExistingProwConfig(configFileLocation, pluginsFileLocation, kubeClient, namespace) 122 if err != nil { 123 return nil, nil, nil, err 124 } 125 126 if prowConfig.LighthouseJobNamespace == "" { 127 prowConfig.LighthouseJobNamespace = "jx" 128 } 129 sourceRepoGroups, sourceRepositories, sourceRepoMap, schedulers, err := BuildSchedulers(prowConfig, pluginConfig) 130 teamSchedulerName = "default-scheduler" 131 cleanupExistingProwConfig(prowConfig, pluginConfig, sourceRepoMap) 132 133 migratedConfigFunc := func(jxClient versioned.Interface, namespace string) (map[string]*jenkinsv1.Scheduler, *jenkinsv1.SourceRepositoryGroupList, *jenkinsv1.SourceRepositoryList, error) { 134 values := []jenkinsv1.SourceRepository{} 135 for _, value := range sourceRepositories { 136 values = append(values, *value) 137 } 138 return schedulers, nil, &jenkinsv1.SourceRepositoryList{Items: values}, nil 139 } 140 if !skipVerification { 141 log.Logger().Info("Verifying generated config") 142 migratedConfig, migratedPlugins, err := GenerateProw(gitOps, false, jxClient, namespace, teamSchedulerName, devEnv, migratedConfigFunc) 143 if err != nil { 144 return nil, nil, nil, errors.Wrap(err, "Error generating prow config") 145 } 146 if dryRun { 147 dumpProwConfigToFiles("migrated", migratedConfig, migratedPlugins) 148 dumpProwConfigToFiles("existing", prowConfig, pluginConfig) 149 } 150 sortSliceStringOpt := cmpopts.SortSlices(func(i string, j string) bool { 151 return i < j 152 }) 153 pluginCmpOptions := cmp.Options{sortSliceStringOpt, 154 cmpopts.SortSlices(func(i plugins.Trigger, j plugins.Trigger) bool { 155 iString := strings.Join(i.Repos, ",") 156 jString := strings.Join(j.Repos, ",") 157 return iString < jString 158 }), cmpopts.SortSlices(func(i plugins.Approve, j plugins.Approve) bool { 159 iString := strings.Join(i.Repos, ",") 160 jString := strings.Join(j.Repos, ",") 161 return iString < jString 162 }), cmpopts.SortSlices(func(i plugins.Lgtm, j plugins.Lgtm) bool { 163 iString := strings.Join(i.Repos, ",") 164 jString := strings.Join(j.Repos, ",") 165 return iString < jString 166 }), 167 } 168 169 if !cmp.Equal(migratedPlugins, pluginConfig, pluginCmpOptions) { 170 return nil, nil, nil, errors.Errorf("Migrated Prow plugins do not match, not applying! \nDiff: \n%s", cmp.Diff(migratedPlugins, pluginConfig, pluginCmpOptions)) 171 } 172 cnfgCmpOptions := cmp.Options{ 173 cmpopts.IgnoreUnexported(job.Brancher{}), 174 cmpopts.IgnoreUnexported(job.RegexpChangeMatcher{}), 175 cmpopts.IgnoreUnexported(job.Presubmit{}), 176 cmpopts.IgnoreUnexported(job.Periodic{}), 177 sortSliceStringOpt, 178 cmpopts.SortSlices(func(i keeper.Query, j keeper.Query) bool { 179 sort.Strings(i.Repos) 180 sort.Strings(j.Repos) 181 iLabels := append(i.Labels, i.MissingLabels...) 182 iString := strings.Join(append(i.Repos, iLabels...), ",") 183 jLabels := append(j.Labels, j.MissingLabels...) 184 jString := strings.Join(append(j.Repos, jLabels...), ",") 185 return iString < jString 186 })} 187 if !cmp.Equal(migratedConfig, prowConfig, cnfgCmpOptions) { 188 return nil, nil, nil, errors.Errorf("Migrated Prow config do not match, not applying! \nDiff: \n%s", cmp.Diff(migratedConfig, prowConfig, cnfgCmpOptions)) 189 } 190 log.Logger().Info("Generated config passed validation") 191 } 192 return sourceRepoGroups, sourceRepositories, schedulers, nil 193 } 194 195 //cleanupExistingProwConfig Removes config that we do not currently support 196 func cleanupExistingProwConfig(prowConfig *config.Config, pluginConfig *plugins.Configuration, sourceRepoMap map[string]*jenkinsv1.SourceRepository) { 197 // heart plugin is not supported 198 pluginConfig.Heart = plugins.Heart{} 199 queries := prowConfig.Keeper.Queries[:0] 200 for _, query := range prowConfig.Keeper.Queries { 201 repos := query.Repos[:0] 202 for _, repo := range query.Repos { 203 // We do not support tide queries for repos with not presubmits or postsubmits, ignore the dummy environment for now 204 if sourceRepoMap[repo] != nil { 205 repos = append(repos, repo) 206 } 207 } 208 if len(repos) > 0 { 209 query.Repos = repos 210 queries = append(queries, query) 211 } 212 } 213 prowConfig.Keeper.Queries = queries 214 215 pluginConfig.ConfigUpdater = plugins.ConfigUpdater{} 216 triggers := make([]plugins.Trigger, 0) 217 for _, trigger := range pluginConfig.Triggers { 218 for _, repo := range trigger.Repos { 219 // We do not support tide queries for repos with not presubmits or postsubmits, ignore the dummy environment for now 220 if sourceRepoMap[repo] != nil { 221 triggers = append(triggers, trigger) 222 } 223 } 224 } 225 pluginConfig.Triggers = triggers 226 227 lgtms := make([]plugins.Lgtm, 0) 228 for _, lgtm := range pluginConfig.Lgtm { 229 for _, repo := range lgtm.Repos { 230 // We do not support tide queries for repos with not presubmits or postsubmits, ignore the dummy environment for now 231 if sourceRepoMap[repo] != nil { 232 newLgtm := lgtm 233 newLgtm.Repos = []string{repo} 234 lgtms = append(lgtms, newLgtm) 235 } 236 } 237 } 238 pluginConfig.Lgtm = lgtms 239 240 approves := make([]plugins.Approve, 0) 241 for _, approve := range pluginConfig.Approve { 242 for _, repo := range approve.Repos { 243 if !strings.Contains(repo, "/") { 244 // Expand the org in to repos for now 245 for existingRepo := range sourceRepoMap { 246 if strings.HasPrefix(existingRepo, repo+"/") { 247 newApprove := approve 248 newApprove.Repos = []string{existingRepo} 249 approves = append(approves, newApprove) 250 } 251 } 252 } else { 253 // We do not support tide queries for repos with not presubmits or postsubmits, ignore the dummy environment for now 254 if sourceRepoMap[repo] != nil { 255 approves = append(approves, approve) 256 } 257 } 258 } 259 } 260 pluginConfig.Approve = approves 261 262 // Branch org protection policy out in to repos 263 for org := range prowConfig.BranchProtection.Orgs { 264 protectionOrg := prowConfig.BranchProtection.Orgs[org] 265 if protectionOrg.Repos == nil { 266 protectionOrg.Repos = make(map[string]branchprotection.Repo) 267 replacedOrgPolicy := false 268 for existingRepo := range sourceRepoMap { 269 if strings.HasPrefix(existingRepo, org+"/") { 270 orgRepo := strings.Split(existingRepo, "/") 271 repoPolicy := branchprotection.Repo{ 272 Policy: protectionOrg.Policy, 273 } 274 protectionOrg.Repos[orgRepo[1]] = repoPolicy 275 prowConfig.BranchProtection.Orgs[org] = protectionOrg 276 replacedOrgPolicy = true 277 } 278 } 279 if replacedOrgPolicy { 280 protectionOrg := prowConfig.BranchProtection.Orgs[org] 281 protectionOrg.Policy = branchprotection.Policy{} 282 prowConfig.BranchProtection.Orgs[org] = protectionOrg 283 } 284 } 285 } 286 287 for repo, plugins := range pluginConfig.ExternalPlugins { 288 if plugins == nil { 289 delete(pluginConfig.ExternalPlugins, repo) 290 continue 291 } 292 if sourceRepoMap[repo] == nil { 293 delete(pluginConfig.ExternalPlugins, repo) 294 } 295 } 296 for repo := range pluginConfig.Plugins { 297 if sourceRepoMap[repo] == nil { 298 delete(pluginConfig.Plugins, repo) 299 } 300 } 301 } 302 303 func dumpProwConfigToFiles(prefix string, prowConfig *config.Config, pluginConfig *plugins.Configuration) { 304 migratedConfigFile := prefix + "Config.yaml" 305 cnfBytes, err := yaml.Marshal(prowConfig) 306 if err != nil { 307 log.Logger().Error("marshaling prow plugins config to yaml") 308 } 309 err = ioutil.WriteFile(migratedConfigFile, cnfBytes, 0600) 310 if err != nil { 311 log.Logger().Errorf("writing %s", migratedConfigFile) 312 } 313 log.Logger().Infof("Writing migrated config to %s", migratedConfigFile) 314 migratedPluginsFile := prefix + "Plugins.yaml" 315 plugsBytes, err := yaml.Marshal(pluginConfig) 316 if err != nil { 317 log.Logger().Error("marshaling prow plugins config to yaml") 318 } 319 err = ioutil.WriteFile(migratedPluginsFile, plugsBytes, 0600) 320 if err != nil { 321 log.Logger().Errorf("writing %s", migratedPluginsFile) 322 } 323 log.Logger().Infof("Writing migrated plugins to %s", migratedPluginsFile) 324 } 325 326 // GenerateSchedulers will generate Pipeline Schedulers from the prow configmaps in the specified namespace 327 func loadExistingProwConfig(configFileLocation string, pluginsFileLocation string, kubeClient kubernetes.Interface, namespace string) (prowConfig *config.Config, pluginConfig *plugins.Configuration, err error) { 328 prowOptions := prow.Options{ 329 KubeClient: kubeClient, 330 NS: namespace, 331 ConfigFileLocation: configFileLocation, 332 PluginsFileLocation: pluginsFileLocation, 333 } 334 if configFileLocation != "" { 335 prowConfig, err = prowOptions.LoadProwConfigFromFile() 336 log.Logger().Infof("Loading prow config from file %s", configFileLocation) 337 } else { 338 prowConfig, err = prowOptions.LoadProwConfig() 339 log.Logger().Info("Loading prow config from configmap") 340 } 341 if err != nil { 342 return nil, nil, errors.Wrap(err, "getting prow config") 343 } 344 if pluginsFileLocation != "" { 345 pluginConfig, err = prowOptions.LoadProwPluginsFromFile() 346 log.Logger().Infof("Loading prow plugins from file %s", pluginsFileLocation) 347 } else { 348 pluginConfig, err = prowOptions.LoadPluginConfig() 349 log.Logger().Info("Loading prow plugins from configmap") 350 } 351 if err != nil { 352 return nil, nil, errors.Wrap(err, "getting prow plugins config") 353 } 354 return prowConfig, pluginConfig, nil 355 } 356 357 func addTeamScheduler(defaultSchedulerName string, defaultScheduler *jenkinsv1.Scheduler, applicableSchedulers []*jenkinsv1.SchedulerSpec) []*jenkinsv1.SchedulerSpec { 358 if defaultScheduler != nil { 359 applicableSchedulers = append([]*jenkinsv1.SchedulerSpec{&defaultScheduler.Spec}, applicableSchedulers...) 360 } else { 361 if defaultSchedulerName != "" { 362 log.Logger().Warnf("A team pipeline scheduler named %s was configured but could not be found", defaultSchedulerName) 363 } 364 } 365 return applicableSchedulers 366 } 367 368 func addRepositoryScheduler(sourceRepo jenkinsv1.SourceRepository, lookup map[string]*jenkinsv1.Scheduler, applicableSchedulers []*jenkinsv1.SchedulerSpec) []*jenkinsv1.SchedulerSpec { 369 if sourceRepo.Spec.Scheduler.Name != "" { 370 scheduler := lookup[sourceRepo.Spec.Scheduler.Name] 371 if scheduler != nil { 372 applicableSchedulers = append([]*jenkinsv1.SchedulerSpec{&scheduler.Spec}, applicableSchedulers...) 373 } else { 374 log.Logger().Warnf("A scheduler named %s is referenced by repository(%s) but could not be found", sourceRepo.Spec.Scheduler.Name, sourceRepo.Name) 375 } 376 } 377 return applicableSchedulers 378 } 379 380 func addProjectSchedulers(sourceRepoGroups *jenkinsv1.SourceRepositoryGroupList, sourceRepo jenkinsv1.SourceRepository, lookup map[string]*jenkinsv1.Scheduler, applicableSchedulers []*jenkinsv1.SchedulerSpec) []*jenkinsv1.SchedulerSpec { 381 if sourceRepoGroups != nil { 382 for _, sourceGroup := range sourceRepoGroups.Items { 383 for _, groupRepo := range sourceGroup.Spec.SourceRepositorySpec { 384 if groupRepo.Name == sourceRepo.Name { 385 if sourceGroup.Spec.Scheduler.Name != "" { 386 scheduler := lookup[sourceGroup.Spec.Scheduler.Name] 387 if scheduler != nil { 388 applicableSchedulers = append([]*jenkinsv1.SchedulerSpec{&scheduler.Spec}, applicableSchedulers...) 389 } else { 390 log.Logger().Warnf("A scheduler named %s is referenced by repository group(%s) but could not be found", sourceGroup.Spec.Scheduler.Name, sourceGroup.Name) 391 } 392 } 393 } 394 } 395 } 396 } 397 return applicableSchedulers 398 } 399 400 func addConfigUpdaterToDevEnv(gitOps bool, autoApplyConfigUpdater bool, applicableSchedulers []*jenkinsv1.SchedulerSpec, devEnv *jenkinsv1.Environment, sourceRepo *jenkinsv1.SourceRepositorySpec) []*jenkinsv1.SchedulerSpec { 401 if gitOps && autoApplyConfigUpdater && strings.Contains(devEnv.Spec.Source.URL, sourceRepo.Org+"/"+sourceRepo.Repo) { 402 maps := make(map[string]jenkinsv1.ConfigMapSpec) 403 maps["env/prow/config.yaml"] = jenkinsv1.ConfigMapSpec{ 404 Name: "config", 405 } 406 maps["env/prow/plugins.yaml"] = jenkinsv1.ConfigMapSpec{ 407 Name: "plugins", 408 } 409 environmentUpdaterSpec := &jenkinsv1.SchedulerSpec{ 410 ConfigUpdater: &jenkinsv1.ConfigUpdater{ 411 Map: maps, 412 }, 413 Plugins: &jenkinsv1.ReplaceableSliceOfStrings{ 414 Items: []string{"config-updater"}, 415 }, 416 } 417 applicableSchedulers = append([]*jenkinsv1.SchedulerSpec{environmentUpdaterSpec}, applicableSchedulers...) 418 } 419 return applicableSchedulers 420 } 421 422 //ApplyDirectly directly applies the prow config to the cluster 423 func ApplyDirectly(kubeClient kubernetes.Interface, namespace string, cfg *config.Config, 424 plugs *plugins.Configuration) error { 425 cfgYaml, err := yaml.Marshal(cfg) 426 if err != nil { 427 return errors.Wrapf(err, "marshalling config to yaml") 428 } 429 cfgConfigMap := &v1.ConfigMap{ 430 ObjectMeta: metav1.ObjectMeta{ 431 Name: "config", 432 Namespace: namespace, 433 }, 434 Data: map[string]string{ 435 "config.yaml": string(cfgYaml), 436 }, 437 } 438 plugsYaml, err := yaml.Marshal(plugs) 439 if err != nil { 440 return errors.Wrapf(err, "marshalling plugins to yaml") 441 } 442 plugsConfigMap := &v1.ConfigMap{ 443 ObjectMeta: metav1.ObjectMeta{ 444 Name: "plugins", 445 Namespace: namespace, 446 }, 447 Data: map[string]string{ 448 "plugins.yaml": string(plugsYaml), 449 }, 450 } 451 _, err = kubeClient.CoreV1().ConfigMaps(namespace).Update(cfgConfigMap) 452 if kubeerrors.IsNotFound(err) { 453 _, err := kubeClient.CoreV1().ConfigMaps(namespace).Create(cfgConfigMap) 454 if err != nil { 455 return errors.Wrapf(err, "creating ConfigMap config") 456 } 457 } else if err != nil { 458 return errors.Wrapf(err, "updating ConfigMap config") 459 } 460 _, err = kubeClient.CoreV1().ConfigMaps(namespace).Update(plugsConfigMap) 461 if kubeerrors.IsNotFound(err) { 462 _, err := kubeClient.CoreV1().ConfigMaps(namespace).Create(plugsConfigMap) 463 if err != nil { 464 return errors.Wrapf(err, "creating ConfigMap plugins") 465 } 466 } else if err != nil { 467 return errors.Wrapf(err, "updating ConfigMap plugins") 468 } 469 return nil 470 } 471 472 //ApplySchedulersDirectly directly applies pipeline schedulers to the cluster 473 func ApplySchedulersDirectly(jxClient versioned.Interface, namespace string, sourceRepositoryGroups []*jenkinsv1.SourceRepositoryGroup, sourceRepositories []*jenkinsv1.SourceRepository, schedulers map[string]*jenkinsv1.Scheduler, devEnv *jenkinsv1.Environment) error { 474 log.Logger().Infof("Applying scheduler configuration to namespace %s", namespace) 475 err := jxClient.JenkinsV1().Schedulers(namespace).DeleteCollection(&metav1.DeleteOptions{}, metav1.ListOptions{}) 476 if err != nil { 477 return errors.Wrapf(err, "Error removing existing schedulers") 478 } 479 for _, scheduler := range schedulers { 480 _, err := jxClient.JenkinsV1().Schedulers(namespace).Update(scheduler) 481 if kubeerrors.IsNotFound(err) { 482 _, err := jxClient.JenkinsV1().Schedulers(namespace).Create(scheduler) 483 if err != nil { 484 return errors.Wrapf(err, "creating scheduler") 485 } 486 } else if err != nil { 487 return errors.Wrapf(err, "updating scheduler") 488 } 489 if scheduler.Name == "default-scheduler" { 490 devEnv.Spec.TeamSettings.DefaultScheduler.Name = scheduler.Name 491 devEnv.Spec.TeamSettings.DefaultScheduler.Kind = "Scheduler" 492 _, err = jxClient.JenkinsV1().Environments(namespace).PatchUpdate(devEnv) 493 if err != nil { 494 return errors.Wrapf(err, "patch updating env %v", devEnv) 495 } 496 } 497 } 498 for _, repo := range sourceRepositories { 499 sourceRepo, err := kube.GetOrCreateSourceRepository(jxClient, namespace, repo.Spec.Repo, repo.Spec.Org, repo.Spec.Provider) 500 if err != nil || sourceRepo == nil { 501 return errors.New("Getting / creating source repo") 502 } 503 sourceRepo.Spec.Scheduler.Name = repo.Spec.Scheduler.Name 504 sourceRepo.Spec.Scheduler.Kind = repo.Spec.Scheduler.Kind 505 _, err = jxClient.JenkinsV1().SourceRepositories(namespace).Update(sourceRepo) 506 if err != nil { 507 return errors.Wrapf(err, "updating source repo") 508 } 509 } 510 for _, repoGroup := range sourceRepositoryGroups { 511 _, err := jxClient.JenkinsV1().SourceRepositoryGroups(namespace).Update(repoGroup) 512 if kubeerrors.IsNotFound(err) { 513 _, err := jxClient.JenkinsV1().SourceRepositoryGroups(namespace).Create(repoGroup) 514 if err != nil { 515 return errors.Wrapf(err, "creating source repo group") 516 } 517 } else if err != nil { 518 return errors.Wrapf(err, "updating source repo group") 519 } 520 } 521 522 return nil 523 } 524 525 //GitOpsOptions are options for running AddToEnvironmentRepo 526 type GitOpsOptions struct { 527 Gitter gits.Gitter 528 Verbose bool 529 Helmer helm.Helmer 530 GitProvider gits.GitProvider 531 DevEnv *jenkinsv1.Environment 532 PullRequestCloneDir string 533 } 534 535 // AddToEnvironmentRepo adds the prow config to the gitops environment repo 536 func (o *GitOpsOptions) AddToEnvironmentRepo(cfg *config.Config, plugs *plugins.Configuration, kubeClient kubernetes.Interface, namespace string) error { 537 branchNameUUID, err := uuid.NewUUID() 538 if err != nil { 539 return errors.Wrapf(err, "creating creating branch name") 540 } 541 validBranchName := "add-prow-config" + branchNameUUID.String() 542 details := gits.PullRequestDetails{ 543 BranchName: validBranchName, 544 Title: fmt.Sprintf("Add Prow config"), 545 Message: fmt.Sprintf("Add Prow config generated on %s", time.Now()), 546 } 547 548 modifyChartFn := func(requirements *helm.Requirements, metadata *chart.Metadata, 549 existingValues map[string]interface{}, 550 templates map[string]string, dir string, pullRequestDetails *gits.PullRequestDetails) error { 551 prowDir := filepath.Join(dir, "prow") 552 err := os.MkdirAll(prowDir, 0700) 553 if err != nil { 554 return errors.Wrapf(err, "creating prow dir in gitops repo %s", prowDir) 555 } 556 cfgBytes, err := yaml.Marshal(cfg) 557 if err != nil { 558 return errors.Wrapf(err, "marshaling prow config to yaml") 559 } 560 cfgPath := filepath.Join(prowDir, "config.yaml") 561 err = ioutil.WriteFile(cfgPath, cfgBytes, 0600) 562 if err != nil { 563 return errors.Wrapf(err, "writing %s", cfgPath) 564 } 565 566 plugsBytes, err := yaml.Marshal(plugs) 567 if err != nil { 568 return errors.Wrapf(err, "marshaling prow plugins config to yaml") 569 } 570 plugsPath := filepath.Join(prowDir, "plugins.yaml") 571 err = ioutil.WriteFile(plugsPath, plugsBytes, 0600) 572 if err != nil { 573 return errors.Wrapf(err, "writing %s", plugsPath) 574 } 575 return nil 576 } 577 578 options := environments.EnvironmentPullRequestOptions{ 579 Gitter: o.Gitter, 580 ModifyChartFn: modifyChartFn, 581 GitProvider: o.GitProvider, 582 } 583 584 info, err := options.Create(o.DevEnv, o.PullRequestCloneDir, &details, nil, "", false) 585 586 if err != nil { 587 return errors.Wrapf(err, "creating pr for prow config") 588 } 589 if info != nil { 590 log.Logger().Infof("Added prow config via Pull Request %s", info.PullRequest.URL) 591 } 592 err = o.RegisterProwConfigUpdater(kubeClient, namespace) 593 if err != nil { 594 return errors.Wrapf(err, "Updating prow configmap") 595 } 596 return nil 597 } 598 599 // RegisterProwConfigUpdater Register the config updater in the plugin configmap 600 func (o *GitOpsOptions) RegisterProwConfigUpdater(kubeClient kubernetes.Interface, namespace string) error { 601 prowOptions := prow.Options{ 602 KubeClient: kubeClient, 603 NS: namespace, 604 } 605 pluginConfig, err := prowOptions.LoadPluginConfig() 606 if err != nil { 607 return errors.Wrapf(err, "getting plugins configmap") 608 } 609 pluginConfig.ConfigUpdater.Maps = make(map[string]plugins.ConfigMapSpec) 610 pluginConfig.ConfigUpdater.Maps["env/prow/config.yaml"] = plugins.ConfigMapSpec{Name: prow.ConfigMapName} 611 pluginConfig.ConfigUpdater.Maps["env/prow/plugins.yaml"] = plugins.ConfigMapSpec{Name: prow.PluginsConfigMapName} 612 pluginYAML, err := yaml.Marshal(pluginConfig) 613 if err != nil { 614 return errors.Wrap(err, "marshaling the prow plugins") 615 } 616 617 data := make(map[string]string) 618 data[prow.PluginsFilename] = string(pluginYAML) 619 cm := &v1.ConfigMap{ 620 Data: data, 621 ObjectMeta: metav1.ObjectMeta{ 622 Name: prow.PluginsConfigMapName, 623 }, 624 } 625 _, err = kubeClient.CoreV1().ConfigMaps(namespace).Update(cm) 626 if err != nil { 627 err = errors.Wrapf(err, "updating the config map %q", prow.PluginsConfigMapName) 628 } 629 return nil 630 } 631 632 // AddSchedulersToEnvironmentRepo adds the prow config to the gitops environment repo 633 func (o *GitOpsOptions) AddSchedulersToEnvironmentRepo(sourceRepositoryGroups []*jenkinsv1.SourceRepositoryGroup, sourceRepositories []*jenkinsv1.SourceRepository, schedulers map[string]*jenkinsv1.Scheduler) error { 634 branchNameUUID, err := uuid.NewUUID() 635 if err != nil { 636 return errors.Wrapf(err, "creating creating branch name") 637 } 638 log.Logger().Info("Saving scheduler config to environment repository") 639 validBranchName := "add-pipeline-schedulers" + branchNameUUID.String() 640 details := gits.PullRequestDetails{ 641 BranchName: validBranchName, 642 Title: fmt.Sprintf("Add pipeline schedulers"), 643 Message: fmt.Sprintf("Add pipeline schedulers generated on %s", time.Now()), 644 } 645 646 modifyChartFn := func(requirements *helm.Requirements, metadata *chart.Metadata, 647 existingValues map[string]interface{}, 648 templates map[string]string, dir string, pullRequestDetails *gits.PullRequestDetails) error { 649 templatesDir := filepath.Join(dir, "templates") 650 for _, repoGroup := range sourceRepositoryGroups { 651 cfgBytes, err := yaml.Marshal(repoGroup) 652 if err != nil { 653 return errors.Wrapf(err, "marshaling source repo group to yaml") 654 } 655 cfgPath := filepath.Join(templatesDir, repoGroup.Name+"-repo-group.yaml") 656 err = ioutil.WriteFile(cfgPath, cfgBytes, 0600) 657 if err != nil { 658 return errors.Wrapf(err, "writing %s", cfgPath) 659 } 660 } 661 662 for _, repo := range sourceRepositories { 663 repoName := naming.ToValidName(repo.Spec.Org + "-" + repo.Spec.Repo) 664 if repo.Name == "" { 665 repo.Name = repoName 666 } 667 cfgBytes, err := yaml.Marshal(repo) 668 if err != nil { 669 return errors.Wrapf(err, "marshaling source repos to yaml") 670 } 671 cfgPath := filepath.Join(templatesDir, repo.Name+"-repo.yaml") 672 err = ioutil.WriteFile(cfgPath, cfgBytes, 0600) 673 if err != nil { 674 return errors.Wrapf(err, "writing %s", cfgPath) 675 } 676 } 677 for _, scheduler := range schedulers { 678 cfgBytes, err := yaml.Marshal(scheduler) 679 if err != nil { 680 return errors.Wrapf(err, "marshaling schedulers to yaml") 681 } 682 cfgPath := filepath.Join(templatesDir, scheduler.Name+"-sch.yaml") 683 err = ioutil.WriteFile(cfgPath, cfgBytes, 0600) 684 if err != nil { 685 return errors.Wrapf(err, "writing %s", cfgPath) 686 } 687 if scheduler.Name == "default-scheduler" { 688 devEnvResource := &jenkinsv1.Environment{} 689 devEnvFile := filepath.Join(templatesDir, "dev-env.yaml") 690 devEnvData, err := ioutil.ReadFile(devEnvFile) 691 if err != nil { 692 return errors.Wrapf(err, "reading dev-env file %s", devEnvFile) 693 } 694 err = yaml.Unmarshal(devEnvData, devEnvResource) 695 if err != nil { 696 return errors.Wrapf(err, "reading dev-env yaml %s", devEnvFile) 697 } 698 devEnvResource.Spec.TeamSettings.DefaultScheduler.Kind = "Scheduler" 699 devEnvResource.Spec.TeamSettings.DefaultScheduler.Name = scheduler.Name 700 devEnvBytes, err := yaml.Marshal(devEnvResource) 701 if err != nil { 702 return errors.Wrapf(err, "reading dev-env resource %s", devEnvFile) 703 } 704 err = ioutil.WriteFile(devEnvFile, devEnvBytes, 0600) 705 if err != nil { 706 return errors.Wrapf(err, "writing %s", devEnvFile) 707 } 708 } 709 } 710 return nil 711 } 712 713 options := environments.EnvironmentPullRequestOptions{ 714 Gitter: o.Gitter, 715 ModifyChartFn: modifyChartFn, 716 GitProvider: o.GitProvider, 717 } 718 719 info, err := options.Create(o.DevEnv, o.PullRequestCloneDir, &details, nil, "", false) 720 721 if err != nil { 722 return errors.Wrapf(err, "creating pr for scheduler config") 723 } 724 if info != nil { 725 log.Logger().Infof("Added pipeline scheduler config via Pull Request %s\n", info.PullRequest.URL) 726 } 727 return nil 728 }