This repository has been archived by the owner on Jun 14, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a tool to cross-validate CI config and OCP config
Signed-off-by: Steve Kuznetsov <skuznets@redhat.com>
- Loading branch information
1 parent
e3e0df3
commit 43c0b7c
Showing
1 changed file
with
184 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"` | ||
} |