diff --git a/cmd/pj-rehearse/main.go b/cmd/pj-rehearse/main.go index ff1b79bf..b6d87774 100644 --- a/cmd/pj-rehearse/main.go +++ b/cmd/pj-rehearse/main.go @@ -12,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" pjapi "k8s.io/test-infra/prow/apis/prowjobs/v1" prowgithub "k8s.io/test-infra/prow/github" + prowplugins "k8s.io/test-infra/prow/plugins" pjdwapi "k8s.io/test-infra/prow/pod-utils/downwardapi" "k8s.io/client-go/rest" @@ -109,6 +110,14 @@ func gracefulExit(suppressFailures bool, message string) int { return 1 } +func loadPluginConfig(releaseRepoPath string) (ret prowplugins.ConfigUpdater, err error) { + agent := prowplugins.ConfigAgent{} + if err = agent.Load(filepath.Join(releaseRepoPath, config.PluginConfigInRepoPath)); err == nil { + ret = agent.Config().ConfigUpdater + } + return +} + func rehearseMain() int { o := gatherOptions() err := validateOptions(o) @@ -162,6 +171,11 @@ func rehearseMain() int { } prConfig := config.GetAllConfigs(o.releaseRepoPath, logger) + pluginConfig, err := loadPluginConfig(o.releaseRepoPath) + if err != nil { + logger.WithError(err).Error("could not load plugin configuration from tested revision of release repo") + return gracefulExit(o.noFail, misconfigurationOutput) + } masterConfig, err := config.GetAllConfigsFromSHA(o.releaseRepoPath, jobSpec.Refs.BaseSHA, logger) if err != nil { logger.WithError(err).Error("could not load configuration from base revision of release repo") @@ -214,7 +228,7 @@ func rehearseMain() int { return gracefulExit(o.noFail, misconfigurationOutput) } - cmManager := config.NewTemplateCMManager(cmClient, prNumber, logger) + cmManager := config.NewTemplateCMManager(cmClient, pluginConfig, prNumber, o.releaseRepoPath, logger) defer func() { if err := cmManager.CleanupCMTemplates(); err != nil { logger.WithError(err).Error("failed to clean up temporary template CM") @@ -224,7 +238,7 @@ func rehearseMain() int { logger.WithError(err).Error("couldn't create template configMap") return gracefulExit(o.noFail, failedSetupOutput) } - if err := cmManager.CreateClusterProfiles(filepath.Join(o.releaseRepoPath, config.ClusterProfilesPath), changedClusterProfiles); err != nil { + if err := cmManager.CreateClusterProfiles(changedClusterProfiles); err != nil { logger.WithError(err).Error("couldn't create cluster profile ConfigMaps") return gracefulExit(o.noFail, failedSetupOutput) } diff --git a/pkg/config/release.go b/pkg/config/release.go index e6088119..d1fe8f4d 100644 --- a/pkg/config/release.go +++ b/pkg/config/release.go @@ -16,6 +16,8 @@ import ( const ( // ConfigInRepoPath is the prow config path from release repo ConfigInRepoPath = "cluster/ci/config/prow/config.yaml" + // PluginConfigInRepoPath is the prow plugin config path from release repo + PluginConfigInRepoPath = "cluster/ci/config/prow/plugins.yaml" // JobConfigInRepoPath is the prowjobs path from release repo JobConfigInRepoPath = "ci-operator/jobs" // CiopConfigInRepoPath is the ci-operator config path from release repo diff --git a/pkg/config/template.go b/pkg/config/template.go index a08b9e76..eb690a4e 100644 --- a/pkg/config/template.go +++ b/pkg/config/template.go @@ -20,6 +20,11 @@ import ( kutilerrors "k8s.io/apimachinery/pkg/util/errors" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + + prowgithub "k8s.io/test-infra/prow/github" + _ "k8s.io/test-infra/prow/hook" + prowplugins "k8s.io/test-infra/prow/plugins" + "k8s.io/test-infra/prow/plugins/updateconfig" ) // CiTemplates is a map of all the changed templates @@ -58,17 +63,27 @@ func getTemplates(templatePath string) (CiTemplates, error) { // TemplateCMManager holds the details needed for the configmap controller type TemplateCMManager struct { - cmclient corev1.ConfigMapInterface - prNumber int - logger *logrus.Entry + cmclient corev1.ConfigMapInterface + configUpdaterCfg prowplugins.ConfigUpdater + prNumber int + releaseRepoPath string + logger *logrus.Entry } // NewTemplateCMManager creates a new TemplateCMManager -func NewTemplateCMManager(cmclient corev1.ConfigMapInterface, prNumber int, logger *logrus.Entry) *TemplateCMManager { +func NewTemplateCMManager( + cmclient corev1.ConfigMapInterface, + configUpdaterCfg prowplugins.ConfigUpdater, + prNumber int, + releaseRepoPath string, + logger *logrus.Entry, +) *TemplateCMManager { return &TemplateCMManager{ - cmclient: cmclient, - prNumber: prNumber, - logger: logger, + cmclient: cmclient, + configUpdaterCfg: configUpdaterCfg, + prNumber: prNumber, + releaseRepoPath: releaseRepoPath, + logger: logger, } } @@ -103,43 +118,54 @@ func (c *TemplateCMManager) CreateCMTemplates(templates CiTemplates) error { return kutilerrors.NewAggregate(errors) } -func (c *TemplateCMManager) CreateClusterProfiles(dir string, profiles []ClusterProfile) error { - var errs []error - for _, p := range profiles { - cm, err := genClusterProfileCM(dir, p) - if err != nil { - errs = append(errs, err) - continue - } - c.logger.WithFields(logrus.Fields{"cluster-profile": cm.ObjectMeta.Name}).Info("creating rehearsal cluster profile ConfigMap") - if err := c.createCM(cm); err != nil { - errs = append(errs, err) - } - } - return kutilerrors.NewAggregate(errs) +type osFileGetter struct { + root string } -func genClusterProfileCM(dir string, profile ClusterProfile) (*v1.ConfigMap, error) { - ret := &v1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: profile.CMName()}, - Data: map[string]string{}, - } - profilePath := filepath.Join(dir, profile.Name) - err := filepath.Walk(profilePath, func(path string, info os.FileInfo, err error) error { - if err != nil || info.IsDir() { +func (g osFileGetter) GetFile(filename string) ([]byte, error) { + return ioutil.ReadFile(filepath.Join(g.root, filename)) +} + +func (c *TemplateCMManager) CreateClusterProfiles(profiles []ClusterProfile) error { + changes := []prowgithub.PullRequestChange{} + for _, profile := range profiles { + err := filepath.Walk(filepath.Join(c.releaseRepoPath, ClusterProfilesPath, profile.Name), func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return err + } + // Failure is impossible per filepath.Walk's API. + if path, err = filepath.Rel(c.releaseRepoPath, path); err == nil { + changes = append(changes, prowgithub.PullRequestChange{ + Filename: path, + Status: string(prowgithub.PullRequestFileModified), + }) + } return err - } - b, err := ioutil.ReadFile(path) + }) if err != nil { return err } - ret.Data[filepath.Base(path)] = string(b) - return nil - }) - if err != nil { - return nil, err } - return ret, nil + var errs []error + for cm, data := range updateconfig.FilterChanges(c.configUpdaterCfg, changes, c.logger) { + profile := strings.TrimPrefix(cm.Name, "cluster-profile-") + for _, p := range profiles { + if p.Name == profile { + profile = p.CMName() + break + } + } + err := c.createCM(&v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: profile}, + Data: map[string]string{}, + }) + if err != nil && !kerrors.IsAlreadyExists(err) { + errs = append(errs, err) + } else if err := updateconfig.Update(osFileGetter{root: c.releaseRepoPath}, c.cmclient, profile, cm.Namespace, data, c.logger); err != nil { + errs = append(errs, err) + } + } + return kutilerrors.NewAggregate(errs) } // CleanupCMTemplates deletes all the configMaps that have been created for the changed templates. diff --git a/pkg/config/template_test.go b/pkg/config/template_test.go index f8ce1761..1735b21b 100644 --- a/pkg/config/template_test.go +++ b/pkg/config/template_test.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "reflect" + "sort" "strconv" "testing" @@ -19,6 +20,8 @@ import ( "k8s.io/apimachinery/pkg/util/diff" "k8s.io/client-go/kubernetes/fake" coretesting "k8s.io/client-go/testing" + + prowplugins "k8s.io/test-infra/prow/plugins" ) const templatesPath = "../../test/pj-rehearse-integration/master/ci-operator/templates" @@ -63,7 +66,7 @@ func TestCreateCleanupCMTemplates(t *testing.T) { return true, nil, nil }) client := cs.CoreV1().ConfigMaps(ns) - cmManager := NewTemplateCMManager(client, 1234, logrus.NewEntry(logrus.New())) + cmManager := NewTemplateCMManager(client, prowplugins.ConfigUpdater{}, 1234, "not_used", logrus.NewEntry(logrus.New())) if err := cmManager.CreateCMTemplates(ciTemplates); err != nil { t.Fatalf("CreateCMTemplates() returned error: %v", err) } @@ -101,45 +104,6 @@ func getBaseCiTemplates(t *testing.T) CiTemplates { return CiTemplates{"test-template.yaml": contents} } -func TestGenClusterProfileCM(t *testing.T) { - dir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - profile := ClusterProfile{ - Name: "test-profile", - TreeHash: "abcdef0123456789abcdef0123456789abcdef01", - } - profilePath := filepath.Join(dir, profile.Name) - if err := os.Mkdir(profilePath, 0775); err != nil { - t.Fatal(err) - } - files := []string{"vars.yaml", "vars-origin.yaml"} - for _, f := range files { - if err := ioutil.WriteFile(filepath.Join(profilePath, f), []byte(f+" content"), 0664); err != nil { - t.Fatal(err) - } - } - cm, err := genClusterProfileCM(dir, profile) - if err != nil { - t.Fatal(err) - } - name := "rehearse-cluster-profile-test-profile-abcde" - if n := cm.ObjectMeta.Name; n != name { - t.Errorf("unexpected name: want %q, got %q", name, n) - } - for _, f := range files { - e, d := f+" content", cm.Data[f] - if d != e { - t.Errorf("unexpected value for key %q: want %q, got %q", f, e, d) - } - } - if t.Failed() { - t.Logf("full CM content: %s", cm.Data) - } -} - func TestCreateClusterProfiles(t *testing.T) { dir, err := ioutil.TempDir("", "") if err != nil { @@ -152,40 +116,68 @@ func TestCreateClusterProfiles(t *testing.T) { {Name: "unchanged", TreeHash: "8012ff51a005eaa8ed8f4c08ccdce580f462fff6"}, } for _, p := range profiles { - if err := os.Mkdir(filepath.Join(dir, p.Name), 0775); err != nil { + path := filepath.Join(dir, ClusterProfilesPath, p.Name) + if err := os.MkdirAll(path, 0775); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(filepath.Join(path, "file"), []byte(p.Name+" content"), 0664); err != nil { t.Fatal(err) } } profiles = profiles[:2] ns := "test" pr := 1234 + configUpdaterCfg := prowplugins.ConfigUpdater{ + Maps: map[string]prowplugins.ConfigMapSpec{ + filepath.Join(ClusterProfilesPath, "profile0", "file"): { + Name: "cluster-profile-profile0", + Namespaces: []string{ns}, + }, + filepath.Join(ClusterProfilesPath, "profile1", "file"): { + Name: "cluster-profile-profile1", + Namespaces: []string{ns}, + }, + filepath.Join(ClusterProfilesPath, "unchanged", "file"): { + Name: "cluster-profile-unchanged", + Namespaces: []string{ns}, + }, + }, + } cs := fake.NewSimpleClientset() client := cs.CoreV1().ConfigMaps(ns) - m := NewTemplateCMManager(client, pr, logrus.NewEntry(logrus.New())) - if err := m.CreateClusterProfiles(dir, profiles); err != nil { + m := NewTemplateCMManager(client, configUpdaterCfg, pr, dir, logrus.NewEntry(logrus.New())) + if err := m.CreateClusterProfiles(profiles); err != nil { t.Fatal(err) } cms, err := client.List(metav1.ListOptions{}) + sort.Slice(cms.Items, func(i, j int) bool { + return cms.Items[i].Name < cms.Items[j].Name + }) if err != nil { t.Fatal(err) } - var names []string - for _, p := range cms.Items { - names = append(names, p.Name) - } - expected := []string{ - "rehearse-cluster-profile-profile0-e92d4", - "rehearse-cluster-profile-profile1-a8c99", - } - if !reflect.DeepEqual(expected, names) { - t.Fatal(diff.ObjectDiff(expected, names)) - } - for _, cm := range cms.Items { - if cm.Labels[createByRehearse] != "true" { - t.Fatalf("%q doesn't have label %s=true", cm.Name, createByRehearse) - } - if cm.Labels[rehearseLabelPull] != strconv.Itoa(pr) { - t.Fatalf("%q doesn't have label %s=%d", cm.Name, rehearseLabelPull, pr) - } + expected := []v1.ConfigMap{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rehearse-cluster-profile-profile0-e92d4", + Namespace: ns, + Labels: map[string]string{ + createByRehearse: "true", + rehearseLabelPull: strconv.Itoa(pr), + }, + }, + Data: map[string]string{"file": "profile0 content"}, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "rehearse-cluster-profile-profile1-a8c99", + Namespace: ns, + Labels: map[string]string{ + createByRehearse: "true", + rehearseLabelPull: strconv.Itoa(pr), + }, + }, + Data: map[string]string{"file": "profile1 content"}, + }} + if !equality.Semantic.DeepEqual(expected, cms.Items) { + t.Fatal(diff.ObjectDiff(expected, cms.Items)) } } diff --git a/pkg/rehearse/jobs.go b/pkg/rehearse/jobs.go index e91bcb25..0dde644a 100644 --- a/pkg/rehearse/jobs.go +++ b/pkg/rehearse/jobs.go @@ -72,7 +72,16 @@ func NewCMClient(clusterConfig *rest.Config, namespace string, dry bool) (corecl if err != nil { return true, nil, fmt.Errorf("failed to convert ConfigMap to YAML: %v", err) } - fmt.Printf("%s", y) + fmt.Print(string(y)) + return false, nil, nil + }) + c.PrependReactor("update", "configmaps", func(action coretesting.Action) (bool, runtime.Object, error) { + cm := action.(coretesting.UpdateAction).GetObject().(*v1.ConfigMap) + y, err := yaml.Marshal([]*v1.ConfigMap{cm}) + if err != nil { + return true, nil, fmt.Errorf("failed to convert ConfigMap to YAML: %v", err) + } + fmt.Print(string(y)) return false, nil, nil }) return c.CoreV1().ConfigMaps(namespace), nil diff --git a/test/pj-rehearse-integration/expected.yaml b/test/pj-rehearse-integration/expected.yaml index eb0d7cb3..38788028 100644 --- a/test/pj-rehearse-integration/expected.yaml +++ b/test/pj-rehearse-integration/expected.yaml @@ -130,6 +130,12 @@ ci.openshift.org/rehearse-pull: "1234" created-by-pj-rehearse: "true" name: rehearse-hnq8xb9r-test-template +- metadata: + creationTimestamp: null + labels: + ci.openshift.org/rehearse-pull: "1234" + created-by-pj-rehearse: "true" + name: rehearse-cluster-profile-test-profile-47224 - data: vars-origin.yaml: | vars-origin.yaml @@ -141,6 +147,7 @@ ci.openshift.org/rehearse-pull: "1234" created-by-pj-rehearse: "true" name: rehearse-cluster-profile-test-profile-47224 + namespace: test-namespace - apiVersion: prow.k8s.io/v1 kind: ProwJob metadata: diff --git a/test/pj-rehearse-integration/master/cluster/ci/config/prow/plugins.yaml b/test/pj-rehearse-integration/master/cluster/ci/config/prow/plugins.yaml new file mode 100644 index 00000000..3bdb2565 --- /dev/null +++ b/test/pj-rehearse-integration/master/cluster/ci/config/prow/plugins.yaml @@ -0,0 +1,5 @@ +config_updater: + maps: + cluster/test-deploy/test-profile/*.yaml: + name: cluster-profile-test-profile + namespace: test-namespace