Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions cmd/check-gh-automation/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,9 +369,6 @@ func gatherModifiedRepos(releaseRepoPath string, logger *logrus.Entry) []string
for _, c := range configs {
path := strings.TrimPrefix(c, config.CiopConfigInRepoPath+"/")
split := strings.Split(path, "/")
if split[1] == ".config.prowgen" {
continue
}
if split[1] == "OWNERS" {
continue
}
Expand Down
39 changes: 5 additions & 34 deletions cmd/ci-operator-prowgen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ func (o *options) process() error {

// generateJobsToDir generates prow job configuration into the dir provided by
// consuming ci-operator configuration.
func (o *options) generateJobsToDir(subDir string, prowConfig map[string]*config.Prowgen) error {
func (o *options) generateJobsToDir(subDir string) error {
generated := map[string]*prowconfig.JobConfig{}
genJobsFunc := generateJobs(o.resolver, prowConfig, generated)
genJobsFunc := generateJobs(o.resolver, generated)
if err := o.OperateOnCIOperatorConfigDir(filepath.Join(o.fromDir, subDir), genJobsFunc); err != nil {
return fmt.Errorf("failed to generate jobs: %w", err)
}
Expand All @@ -120,45 +120,17 @@ func (o *options) generateJobsToDir(subDir string, prowConfig map[string]*config
return writeToDir(o.toDir, generated)
}

func generateJobs(resolver registry.Resolver, cache map[string]*config.Prowgen, output map[string]*prowconfig.JobConfig) func(configSpec *cioperatorapi.ReleaseBuildConfiguration, info *config.Info) error {
func generateJobs(resolver registry.Resolver, output map[string]*prowconfig.JobConfig) func(configSpec *cioperatorapi.ReleaseBuildConfiguration, info *config.Info) error {
return func(configSpec *cioperatorapi.ReleaseBuildConfiguration, info *config.Info) error {
orgRepo := fmt.Sprintf("%s/%s", info.Org, info.Repo)
pInfo := &prowgen.ProwgenInfo{Metadata: info.Metadata, Config: config.Prowgen{Private: false, Expose: false}}
var ok bool
var err error
var orgConfig, repoConfig *config.Prowgen

if orgConfig, ok = cache[info.Org]; !ok {
if cache[info.Org], err = config.LoadProwgenConfig(info.OrgPath); err != nil {
return err
}
orgConfig = cache[info.Org]
}

if repoConfig, ok = cache[orgRepo]; !ok {
if cache[orgRepo], err = config.LoadProwgenConfig(info.RepoPath); err != nil {
return err
}
repoConfig = cache[orgRepo]
}

switch {
case orgConfig != nil:
pInfo.Config = *orgConfig
if repoConfig != nil {
pInfo.Config.MergeDefaults(repoConfig)
}
case repoConfig != nil:
pInfo.Config = *repoConfig
}
if resolver != nil {
resolved, err := registry.ResolveConfig(resolver, *configSpec)
if err != nil {
return fmt.Errorf("failed to resolve configuration: %w", err)
}
configSpec = &resolved
}
generated, err := prowgen.GenerateJobs(configSpec, pInfo)
generated, err := prowgen.GenerateJobs(configSpec, &info.Metadata)
if err != nil {
return err
}
Expand Down Expand Up @@ -227,10 +199,9 @@ func main() {
args = append(args, "")
}
logger := logrus.WithFields(logrus.Fields{"target": opt.toDir, "source": opt.fromDir})
config := map[string]*config.Prowgen{}
for _, subDir := range args {
logger = logger.WithFields(logrus.Fields{"subdir": subDir})
if err := opt.generateJobsToDir(subDir, config); err != nil {
if err := opt.generateJobsToDir(subDir); err != nil {
logger.WithError(err).Fatal("Failed to generate jobs")
}
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/ci-operator-prowgen/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"strings"
"testing"

"github.com/openshift/ci-tools/pkg/config"
"github.com/openshift/ci-tools/pkg/testhelper"
)

Expand Down Expand Up @@ -293,7 +292,7 @@ tests:
}

o := options{fromDir: fullConfigPath, toDir: baseProwConfigDir}
if err := o.generateJobsToDir("", map[string]*config.Prowgen{}); err != nil {
if err := o.generateJobsToDir(""); err != nil {
t.Fatalf("Unexpected error generating jobs from config: %v", err)
}

Expand Down
5 changes: 2 additions & 3 deletions cmd/multi-pr-prow-plugin/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,7 @@ func (s *server) generateProwJob(jr jobRun) (*prowv1.ProwJob, error) {
test.Timeout.Duration = defaultMultiRefJobTimeout
}

fakeProwgenInfo := &prowgen.ProwgenInfo{Metadata: testJobMetadata}
jobBaseGen := prowgen.NewProwJobBaseBuilderForTest(ciopConfig, fakeProwgenInfo, prowgen.NewCiOperatorPodSpecGenerator(), test)
jobBaseGen := prowgen.NewProwJobBaseBuilderForTest(ciopConfig, &testJobMetadata, prowgen.NewCiOperatorPodSpecGenerator(), test)
jobBaseGen.PodSpec.Add(prowgen.InjectTestFrom(&jr.JobMetadata))
jobBaseGen.PodSpec.Add(prowgen.CustomHashInput(jobName))

Expand All @@ -378,7 +377,7 @@ func (s *server) generateProwJob(jr jobRun) (*prowv1.ProwJob, error) {
}
jobBaseGen.Cluster(api.Cluster(cluster))

periodic = prowgen.GeneratePeriodicForTest(jobBaseGen, fakeProwgenInfo)
periodic = prowgen.GeneratePeriodicForTest(jobBaseGen, &testJobMetadata)
break
}
if periodic == nil {
Expand Down
190 changes: 0 additions & 190 deletions pkg/config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,198 +6,17 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"slices"
"strings"

"github.com/ghodss/yaml"
"github.com/sirupsen/logrus"

utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
prowv1 "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"

cioperatorapi "github.com/openshift/ci-tools/pkg/api"
"github.com/openshift/ci-tools/pkg/util"
"github.com/openshift/ci-tools/pkg/util/gzip"
"github.com/openshift/ci-tools/pkg/validation"
)

// ProwgenFile is the name of prowgen's configuration file.
var ProwgenFile = ".config.prowgen"

// Prowgen holds the information of the prowgen's configuration file.
type Prowgen struct {
// Private indicates that generated jobs should be marked as hidden
// from display in deck and that they should mount appropriate git credentials
// to clone the repository under test.
Private bool `json:"private,omitempty"`
// Expose declares that jobs should not be hidden from view in deck if they
// are private.
// This field has no effect if private is not set.
Expose bool `json:"expose,omitempty"`
// Rehearsals declares any disabled rehearsals for jobs
Rehearsals Rehearsals `json:"rehearsals,omitempty"`
// SlackReporterConfigs defines all desired slack reporter info for included jobs
SlackReporterConfigs []SlackReporterConfig `json:"slack_reporter,omitempty"`
// SkipOperatorPresubmits allow users to skip the presubmit generation for that specific variant
SkipOperatorPresubmits []SkipOperatorPresubmits `json:"skip_operator_presubmits,omitempty"`
// EnableSecretsStoreCSIDriver indicates that jobs should use the new CSI Secrets Store
// mechanism to handle multi-stage credentials secrets.
EnableSecretsStoreCSIDriver bool `json:"enable_secrets_store_csi_driver,omitempty"`
}

// SlackReporterConfig groups test names to a channel to report; mimicking Prow's version, with some unnecessary fields removed
type SlackReporterConfig struct {
Channel string `json:"channel,omitempty"`
JobStatesToReport []prowv1.ProwJobState `json:"job_states_to_report,omitempty"`
ReportTemplate string `json:"report_template,omitempty"`
// JobNames matches against test names (e.g., "unit", "e2e") not full Prow job names.
// This is intentional for backward compatibility - existing configs use test names here.
JobNames []string `json:"job_names,omitempty"`
// JobNamePatterns are regex patterns that match against test names (e.g., ".*-e2e$").
// Like JobNames, these match test names, not full Prow job names.
JobNamePatterns []string `json:"job_name_patterns,omitempty"`
// ExcludedVariants is a list of variants to skip (e.g., ["hypershift", "okd"])
ExcludedVariants []string `json:"excluded_variants,omitempty"`
// ExcludedJobPatterns are regex patterns that match against FULL Prow job names
// (e.g., "^pull-.*-skip$" or "^periodic-"). This lets you exclude specific job types
// or use prefixes that only exist in the full job name, not the test name.
ExcludedJobPatterns []string `json:"excluded_job_patterns,omitempty"`
}

type SkipOperatorPresubmits struct {
Branch string `json:"branch,omitempty"`
Variant string `json:"variant,omitempty"`
}

// GetSlackReporterConfigForJobName checks against full job names, allowing excluded_job_patterns
// to work with prefixes like "pull-", "periodic-", etc.
func (p *Prowgen) GetSlackReporterConfigForJobName(fullJobName, testName, variant string) *SlackReporterConfig {
nextSlackReporterConfig:
for _, s := range p.SlackReporterConfigs {
if slices.Contains(s.ExcludedVariants, variant) {
continue
}

// Check if job is excluded by pattern (using full job name)
for _, excludePattern := range s.ExcludedJobPatterns {
if matched, err := regexp.MatchString(excludePattern, fullJobName); err == nil && matched {
continue nextSlackReporterConfig
}
}

// Check exact job name matches first (against test name for backward compatibility)
if slices.Contains(s.JobNames, testName) {
return &s
}

// Check regex pattern matches (against test name for backward compatibility)
for _, pattern := range s.JobNamePatterns {
if matched, err := regexp.MatchString(pattern, testName); err == nil && matched {
return &s
}
}
}
return nil
}

func (p *Prowgen) MergeDefaults(defaults *Prowgen) {
if defaults.Private {
p.Private = true
}
if defaults.Expose {
p.Expose = true
}
if defaults.EnableSecretsStoreCSIDriver {
p.EnableSecretsStoreCSIDriver = true
}
if defaults.Rehearsals.DisableAll {
p.Rehearsals.DisableAll = true
}
p.Rehearsals.DisabledRehearsals = append(p.Rehearsals.DisabledRehearsals, defaults.Rehearsals.DisabledRehearsals...)
}

func LoadProwgenConfig(folder string) (*Prowgen, error) {
var pConfig *Prowgen
path := filepath.Join(folder, ProwgenFile)
b, err := os.ReadFile(path)
if err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("prowgen config found in path %s but couldn't read the file: %w", path, err)
}

if err == nil {
if err := yaml.Unmarshal(b, &pConfig); err != nil {
return nil, fmt.Errorf("prowgen config found in path %sbut couldn't unmarshal it: %w", path, err)
}
}

if pConfig != nil {
if err := validateProwgenConfig(pConfig); err != nil {
return nil, fmt.Errorf("prowgen config found in path %s, but it is invalid: %w", path, err)
}
}

return pConfig, nil
}

func validateProwgenConfig(pConfig *Prowgen) error {
var errs []error
if len(pConfig.SlackReporterConfigs) > 1 { // There is no reason to validate if we only have one slack_reporter_config
jobsSeen := sets.NewString()
patternsSeen := sets.NewString()

for _, sc := range pConfig.SlackReporterConfigs {
// Validate exact job names
for _, job := range sc.JobNames {
if jobsSeen.Has(job) {
errs = append(errs, fmt.Errorf("job: %s exists in multiple slack_reporter_configs, it should only be in one", job))
continue
}
jobsSeen.Insert(job)
}

// Validate regex patterns
for _, pattern := range sc.JobNamePatterns {
// Check if regex pattern is valid
if _, err := regexp.Compile(pattern); err != nil {
errs = append(errs, fmt.Errorf("invalid regex pattern: %s, error: %w", pattern, err))
continue
}

// Check for duplicate patterns
if patternsSeen.Has(pattern) {
errs = append(errs, fmt.Errorf("regex pattern: %s exists in multiple slack_reporter_configs, it should only be in one", pattern))
continue
}
patternsSeen.Insert(pattern)
}

// Validate excluded job patterns
for _, pattern := range sc.ExcludedJobPatterns {
// Check if regex pattern is valid
if _, err := regexp.Compile(pattern); err != nil {
errs = append(errs, fmt.Errorf("invalid excluded job pattern: %s, error: %w", pattern, err))
continue
}

// Note: We don't check for duplicates in excluded patterns as it's reasonable
// to have the same exclusion in multiple configs
}
}
}
return utilerrors.NewAggregate(errs)
}

type Rehearsals struct {
// DisableAll indicates that all jobs will not have their "can-be-rehearsed" label set
// and therefore will not be picked up for rehearsals.
DisableAll bool `json:"disable_all,omitempty"`
// DisabledRehearsals contains a list of jobs that will not have their "can-be-rehearsed" label set
// and therefore will not be picked up for rehearsals.
DisabledRehearsals []string `json:"disabled_rehearsals,omitempty"`
}

func readCiOperatorConfig(configFilePath string, info Info) (*cioperatorapi.ReleaseBuildConfiguration, error) {
data, err := gzip.ReadFileMaybeGZIP(configFilePath)
if err != nil {
Expand Down Expand Up @@ -496,12 +315,3 @@ func LoadByOrgRepo(path string) (ByOrgRepo, error) {
}
return config, nil
}

func (p *Prowgen) SkipPresubmits(branch string, variant string) bool {
for _, skip := range p.SkipOperatorPresubmits {
if skip.Branch == branch && skip.Variant == variant {
return true
}
}
return false
}
Loading