Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Before branching day: step 2 #2866

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1 @@
generated-release-gating-jobs
danilo-gemoli marked this conversation as resolved.
Show resolved Hide resolved
@@ -0,0 +1,14 @@
# Generated release gating jobs manager
This manager attempts to automatize step (2.) of "[Few weeks before branching day](https://docs.google.com/document/d/1Z6ejnDCOCvNv9PWkyNPzVbjuLbDMAAT5GEeDpzb0SMs/edit#heading=h.r9xn02r1cyfn)" phase.

## Usage
### Options:
- `--current-release` specifies the current OCP version
- `--release-repo` is the absolution path to `openshift/release` repository

### Example
```sh
$ ./generated-release-gating-jobs \
--current-release "4.12" \
--release-repo "/full/path/to/openshift/release/repo"
```
84 changes: 84 additions & 0 deletions cmd/branchingconfigmanagers/generated-release-gating-jobs/main.go
@@ -0,0 +1,84 @@
package main

import (
"errors"
"flag"
"fmt"
"path"
"time"

"github.com/sirupsen/logrus"

utilerrors "k8s.io/apimachinery/pkg/util/errors"

"github.com/openshift/ci-tools/pkg/api/ocplifecycle"
"github.com/openshift/ci-tools/pkg/branchcuts/bumper"
cioperatorcfg "github.com/openshift/ci-tools/pkg/config"
)

const (
releaseJobsPath = "ci-operator/config/openshift/release"
)

type options struct {
curOCPVersion string
releaseRepoDir string
logLevel int
newIntervalValue int
}

func gatherOptions() (*options, error) {
var errs []error
o := &options{}
flag.StringVar(&o.curOCPVersion, "current-release", "", "Current OCP version")
flag.StringVar(&o.releaseRepoDir, "release-repo", "", "Path to 'openshift/release/ folder")
flag.IntVar(&o.newIntervalValue, "interval", 168, "New interval to set")
flag.IntVar(&o.logLevel, "log-level", int(logrus.DebugLevel), "Log level")
flag.Parse()

if _, err := ocplifecycle.ParseMajorMinor(o.curOCPVersion); o.curOCPVersion != "" && err != nil {
errs = append(errs, fmt.Errorf("error parsing current-release %s", o.curOCPVersion))
}

if o.newIntervalValue < 0 {
errs = append(errs, errors.New("error parsing interval: value is not a positive integer"))
}

if o.releaseRepoDir != "" {
if !path.IsAbs(o.releaseRepoDir) {
errs = append(errs, errors.New("error parsing release repo path: path has to be absolute"))
}
} else {
errs = append(errs, errors.New("error parsing release repo path: path is mandatory"))
}

return o, utilerrors.NewAggregate(errs)
}

func main() {
o, err := gatherOptions()
if err != nil {
logrus.WithError(err).Fatal("failed to gather options")
}

logrus.SetLevel(logrus.Level(o.logLevel))
logrus.Debugf("using options %+v", o)

if err := reconcile(time.Now(), o); err != nil {
logrus.WithError(err).Fatal("failed to reconcile the status")
}
logrus.Info("status reconciled")
}

func reconcile(now time.Time, o *options) error {
logrus.Debugf("using options %+v", o)
releaseJobsDir := path.Join(o.releaseRepoDir, releaseJobsPath)
b, err := bumper.NewGeneratedReleaseGatingJobsBumper(o.curOCPVersion, releaseJobsDir, o.newIntervalValue)
if err != nil {
return fmt.Errorf("new bumper: %w", err)
}
if err := bumper.Bump[*cioperatorcfg.DataWithInfo](b, &bumper.BumpingOptions{}); err != nil {
return fmt.Errorf("bumper: %w", err)
}
return nil
}
241 changes: 241 additions & 0 deletions pkg/branchcuts/bumper/gen-release-jobs-bumper.go
@@ -0,0 +1,241 @@
package bumper

import (
"fmt"
"io/fs"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"

"github.com/sirupsen/logrus"

cioperatorapi "github.com/openshift/ci-tools/pkg/api"
"github.com/openshift/ci-tools/pkg/api/ocplifecycle"
cioperatorcfg "github.com/openshift/ci-tools/pkg/config"
)

const (
releaseJobsRegexPatternFormat = `openshift-release-master__(okd|ci|nightly|okd-scos)-%s.*\.yaml`
ocpReleaseEnvVarName = "OCP_VERSION"
)

type GeneratedReleaseGatingJobsBumper struct {
mm *ocplifecycle.MajorMinor
getFilesRegexp *regexp.Regexp
jobsDir string
newIntervalValue int
}

var _ Bumper[*cioperatorcfg.DataWithInfo] = &GeneratedReleaseGatingJobsBumper{}

func NewGeneratedReleaseGatingJobsBumper(ocpVer, jobsDir string, newIntervalValue int) (*GeneratedReleaseGatingJobsBumper, error) {
mm, err := ocplifecycle.ParseMajorMinor(ocpVer)
if err != nil {
return nil, fmt.Errorf("parse release: %w", err)
}
mmRegexp := fmt.Sprintf("%d\\.%d", mm.Major, mm.Minor)
getFilesRegexp := regexp.MustCompile(fmt.Sprintf(releaseJobsRegexPatternFormat, mmRegexp))
return &GeneratedReleaseGatingJobsBumper{
mm,
getFilesRegexp,
jobsDir,
newIntervalValue,
}, nil
}

func (b *GeneratedReleaseGatingJobsBumper) GetFiles() ([]string, error) {
files := make([]string, 0)
err := filepath.Walk(b.jobsDir, func(path string, info fs.FileInfo, err error) error {
if b.getFilesRegexp.Match([]byte(path)) {
files = append(files, path)
}
return nil
})
return files, err
}

func (b *GeneratedReleaseGatingJobsBumper) Unmarshall(file string) (*cioperatorcfg.DataWithInfo, error) {
cfgDataByFilename, err := cioperatorcfg.LoadDataByFilename(file)
if err != nil {
return nil, err
}

filename := path.Base(file)
dataWithInfo, ok := cfgDataByFilename[filename]
if !ok {
logrus.WithError(err).Errorf("failed to get config %s", file)
return nil, fmt.Errorf("can't get data %s", filename)
}
return &dataWithInfo, nil
}

func (b *GeneratedReleaseGatingJobsBumper) BumpFilename(
filename string,
dataWithInfo *cioperatorcfg.DataWithInfo) (string, error) {
newVariant, err := ReplaceWithNextVersion(dataWithInfo.Info.Metadata.Variant, b.mm.Major)
if err != nil {
return "", err
}
dataWithInfo.Info.Metadata.Variant = newVariant
return dataWithInfo.Info.Metadata.Basename(), nil
}

/*
Candidate bumping fields:
.base_images.*.name
.releases.*.{release,candidate}.version
.releases.*.prerelease.version_bounds.{lower,upper}
.tests[].steps.test[].env[].default
*/
func (b *GeneratedReleaseGatingJobsBumper) BumpContent(dataWithInfo *cioperatorcfg.DataWithInfo) (*cioperatorcfg.DataWithInfo, error) {
major := b.mm.Major
config := &dataWithInfo.Configuration
if err := bumpBaseImages(config, major); err != nil {
return nil, err
}

if err := bumpReleases(config, major); err != nil {
return nil, err
}

if err := bumpTests(config, major); err != nil {
return nil, err
}

if err := ReplaceWithNextVersionInPlace(&config.Metadata.Variant, major); err != nil {
return nil, err
}

if config.Tests != nil {
for i := 0; i < len(config.Tests); i++ {
if config.Tests[i].Interval != nil {
*config.Tests[i].Interval = strconv.Itoa(b.newIntervalValue) + "h"
}
}
}

return dataWithInfo, nil
}

func bumpBaseImages(config *cioperatorapi.ReleaseBuildConfiguration, major int) error {
if config.BaseImages == nil {
return nil
}

bumpedImages := make(map[string]cioperatorapi.ImageStreamTagReference)
for k := range config.BaseImages {
image := config.BaseImages[k]
if err := ReplaceWithNextVersionInPlace(&image.Name, major); err != nil {
return err
}
bumpedImages[k] = image
}
config.BaseImages = bumpedImages
return nil
}

func bumpReleases(config *cioperatorapi.ReleaseBuildConfiguration, major int) error {
if config.Releases == nil {
return nil
}

bumpedReleases := make(map[string]cioperatorapi.UnresolvedRelease)
for k := range config.Releases {
release := config.Releases[k]
if release.Release != nil {
if err := ReplaceWithNextVersionInPlace(&release.Release.Version, major); err != nil {
return err
}
}
if release.Candidate != nil {
if err := ReplaceWithNextVersionInPlace(&release.Candidate.Version, major); err != nil {
return err
}
}
if release.Prerelease != nil {
if err := ReplaceWithNextVersionInPlace(&release.Prerelease.VersionBounds.Upper, major); err != nil {
return err
}
if err := ReplaceWithNextVersionInPlace(&release.Prerelease.VersionBounds.Lower, major); err != nil {
return err
}
}
bumpedReleases[k] = release
}
config.Releases = bumpedReleases
return nil
}

func bumpTests(config *cioperatorapi.ReleaseBuildConfiguration, major int) error {
if config.Tests == nil {
return nil
}

for i := 0; i < len(config.Tests); i++ {
test := config.Tests[i]

if test.MultiStageTestConfiguration == nil {
continue
}

if err := bumpTestSteps(test.MultiStageTestConfiguration.Pre, major); err != nil {
return err
}
if err := bumpTestSteps(test.MultiStageTestConfiguration.Test, major); err != nil {
return err
}
if err := bumpTestSteps(test.MultiStageTestConfiguration.Post, major); err != nil {
return err
}

if err := ReplaceWithNextVersionInPlace(test.MultiStageTestConfiguration.Workflow, major); err != nil {
return err
}

config.Tests[i] = test
}

return nil
}

func bumpTestSteps(tests []cioperatorapi.TestStep, major int) error {
if tests == nil {
return nil
}
for i := 0; i < len(tests); i++ {
multistageTest := tests[i]
if err := bumpTestStepEnvVars(multistageTest, major); err != nil {
return err
}
tests[i] = multistageTest
}
return nil
}

func bumpTestStepEnvVars(multistageTest cioperatorapi.TestStep, major int) error {
if multistageTest.LiteralTestStep == nil || multistageTest.LiteralTestStep.Environment == nil {
return nil
}
for i := 0; i < len(multistageTest.Environment); i++ {
env := multistageTest.Environment[i]
if env.Name == ocpReleaseEnvVarName {
if err := ReplaceWithNextVersionInPlace(env.Default, major); err != nil {
return err
}
}
multistageTest.Environment[i] = env
}
return nil
}

func (b *GeneratedReleaseGatingJobsBumper) Marshall(dataWithInfo *cioperatorcfg.DataWithInfo, bumpedFilename, dir string) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use a better name for this method since it is not marshalling anything.

absolutePath := path.Join(dir, bumpedFilename)
dirNoOrgRepo := strings.TrimSuffix(absolutePath, dataWithInfo.Info.Metadata.RelativePath())
if err := dataWithInfo.CommitTo(dirNoOrgRepo); err != nil {
logrus.WithError(err).Errorf("error saving config %s", dirNoOrgRepo)
return fmt.Errorf("commit to %s failed: %w", dirNoOrgRepo, err)
}
return nil
}