Skip to content
This repository has been archived by the owner on Jun 14, 2019. It is now read-only.

Commit

Permalink
Add a tool to cross-validate CI config and OCP config
Browse files Browse the repository at this point in the history
Signed-off-by: Steve Kuznetsov <skuznets@redhat.com>
  • Loading branch information
stevekuznetsov committed Mar 22, 2019
1 parent e3e0df3 commit 43c0b7c
Showing 1 changed file with 184 additions and 0 deletions.
184 changes: 184 additions & 0 deletions cmd/promotion-validator/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package main

import (
"errors"
"flag"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"

"github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"

"github.com/openshift/ci-operator/pkg/api"
"k8s.io/apimachinery/pkg/util/sets"

"github.com/openshift/ci-operator-prowgen/pkg/config"
"github.com/openshift/ci-operator-prowgen/pkg/diffs"
"github.com/openshift/ci-operator-prowgen/pkg/promotion"
)

type options struct {
currentRelease string
releaseRepoDir string
ocpBuildDataRepoDir string

logLevel string
}

func (o *options) Validate() error {
if o.releaseRepoDir == "" {
return errors.New("required flag --release-repo-dir was unset")
}

if o.ocpBuildDataRepoDir == "" {
return errors.New("required flag --ocp-build-data-repo-dir was unset")
}

if o.currentRelease == "" {
return errors.New("required flag --current-release was unset")
}

level, err := logrus.ParseLevel(o.logLevel)
if err != nil {
return fmt.Errorf("invalid --log-level: %v", err)
}
logrus.SetLevel(level)
return nil
}

func (o *options) Bind(fs *flag.FlagSet) {
fs.StringVar(&o.currentRelease, "current-release", "", "Configurations targeting this release will get validated.")
fs.StringVar(&o.releaseRepoDir, "release-repo-dir", "", "Path to openshift/release repo.")
fs.StringVar(&o.ocpBuildDataRepoDir, "ocp-build-data-repo-dir", "", "Path to openshift/ocp-build-data repo.")
fs.StringVar(&o.logLevel, "log-level", "info", "Level at which to log output.")
}

func gatherOptions() options {
o := options{}
fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
o.Bind(fs)
if err := fs.Parse(os.Args[1:]); err != nil {
logrus.WithError(err).Fatal("could not parse input")
}
return o
}

func main() {
o := gatherOptions()
if err := o.Validate(); err != nil {
logrus.Fatalf("Invalid options: %v", err)
}

imageConfigByName := map[string]imageConfig{}
if err := filepath.Walk(filepath.Join(o.ocpBuildDataRepoDir, "images"), func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}

// we know the path is relative, but there is no API to declare that
relPath, _ := filepath.Rel(o.ocpBuildDataRepoDir, path)
logger := logrus.WithField("source-file", relPath)
raw, err := ioutil.ReadFile(path)
if err != nil {
logger.WithError(err).Fatal("Could not load OCP build data configuration.")
}

var productConfig imageConfig
if err := yaml.Unmarshal(raw, &productConfig); err != nil {
logger.WithError(err).Fatal("Could not unmarshal OCP build data configuration.")
}
productConfig.path = relPath

imageConfigByName[productConfig.Name] = productConfig
return nil
}); err != nil {
logrus.WithError(err).Fatal("Could walk OCP build data configuration directory.")
}

var foundFailures bool
if err := config.OperateOnCIOperatorConfigDir(path.Join(o.releaseRepoDir, diffs.CIOperatorConfigInRepoPath), func(configuration *api.ReleaseBuildConfiguration, info *config.Info) error {
if !(promotion.PromotesOfficialImages(configuration) && configuration.PromotionConfiguration.Name == o.currentRelease) {
return nil
}
logger := config.LoggerForInfo(*info)

for _, image := range configuration.Images {
if image.Optional {
continue
}
logger = logger.WithField("image", image.To)
productImageName := fmt.Sprintf("openshift/ose-%s", image.To)
logger.Debug("Validating image.")
productConfig, exists := imageConfigByName[productImageName]
if !exists {
logger.WithField("ocp-build-data-value", productImageName).Error("No configuration for the expected product image found in OCP build data.")
continue
}
logger = logger.WithField("ocp-build-data-path", productConfig.path)

if actual, expected := info.Branch, productConfig.Content.Source.Git.Branch.Fallback; actual != expected {
logger = logger.WithFields(logrus.Fields{"ci-operator-value": actual, "ocp-build-data-value": expected})
if actual == "" {
logger.Error("Branch not set in CI Operator configuration.")
} else if expected == "" {
logger.Error("Branch not set in OCP build data configuration.")
} else {
logger.Error("Branch in CI Operator configuration does not match that for OCP build data.")
}
foundFailures = true
}

// there is no standard, we just need to generally point at the right thing
urls := []string{
fmt.Sprintf("git@github.com:%s/%s", info.Org, info.Repo),
fmt.Sprintf("git@github.com:%s/%s.git", info.Org, info.Repo),
fmt.Sprintf("https://github.com/%s/%s", info.Org, info.Repo),
}
if actual, expected := productConfig.Content.Source.Git.Url, sets.NewString(urls...); !expected.Has(actual) {
logger = logger.WithFields(logrus.Fields{"ci-operator-values": expected.List(), "ocp-build-data-value": actual})
if actual == "" {
logger.Error("Repo URL not set in OCP build data configuration.")
} else {
logger.Error("Repo URL in CI Operator configuration does not match that for OCP build data.")
}
foundFailures = true
}
}
return nil
}); err != nil {
logrus.WithError(err).Fatal("Could not load CI Operator configurations.")
}

if foundFailures {
logrus.Fatal("Found configurations that promote to official streams but do not have corresponding OCP build data configurations.")
}
}

// imageConfig is the configuration stored in the ocp-build-data repository
type imageConfig struct {
Content content `json:"content"`
Name string `json:"name"`

// added by us
path string
}

type content struct {
Source source `json:"source"`
}

type source struct {
Git git `json:"git"`
}

type git struct {
Branch branch `json:"branch"`
Url string `json:"url"`
}

type branch struct {
Fallback string `json:"fallback"`
}

0 comments on commit 43c0b7c

Please sign in to comment.