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

Update how ci-operator gets cluster profile secrets #4112

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
67 changes: 65 additions & 2 deletions cmd/ci-operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ type options struct {
leaseServerCredentialsFile string
leaseAcquireTimeout time.Duration
leaseClient lease.Client
clusterProfiles []clusterProfileForTarget

givePrAuthorAccessToNamespace bool
impersonateUser string
Expand Down Expand Up @@ -646,6 +647,8 @@ func (o *options) Complete() error {
o.secrets = append(o.secrets, secret)
}

o.getClusterProfileNamesFromTargets()

for _, path := range o.templatePaths.values {
contents, err := os.ReadFile(path)
if err != nil {
Expand Down Expand Up @@ -1179,11 +1182,11 @@ func (o *options) initializeNamespace() error {
if err != nil {
return fmt.Errorf("could not get project client for cluster config: %w", err)
}
client, err := ctrlruntimeclient.New(o.clusterConfig, ctrlruntimeclient.Options{})
ctrlClient, err := ctrlruntimeclient.New(o.clusterConfig, ctrlruntimeclient.Options{})
if err != nil {
return fmt.Errorf("failed to construct client: %w", err)
}
client = ctrlruntimeclient.NewNamespacedClient(client, o.namespace)
client := ctrlruntimeclient.NewNamespacedClient(ctrlClient, o.namespace)
ctx := context.Background()

logrus.Debugf("Creating namespace %s", o.namespace)
Expand Down Expand Up @@ -1456,6 +1459,17 @@ func (o *options) initializeNamespace() error {
}
}

// adds the appropriate cluster profile secrets to o.secrets,
// so they can be created by ctrlruntime client in the for cycle below this one
for _, cp := range o.clusterProfiles {
cpSecret, err := getClusterProfileSecret(cp, ctrlClient, o.resolverClient, ctx)
if err != nil {
return fmt.Errorf("failed to create cluster profile secret %s: %w", cp, err)
}
cpSecret.Namespace = o.namespace
o.secrets = append(o.secrets, cpSecret)
}

for _, secret := range o.secrets {
created, err := util.UpsertImmutableSecret(ctx, client, secret)
if err != nil {
Expand Down Expand Up @@ -2312,3 +2326,52 @@ func addSchemes() error {
}
return nil
}

// getClusterProfileSecret retrieves the cluster profile secret name using config resolver,
// and gets the secret from the ci namespace
func getClusterProfileSecret(cp clusterProfileForTarget, client ctrlruntimeclient.Client, resolverClient server.ResolverClient, ctx context.Context) (*coreapi.Secret, error) {
// Use config-resolver to get details about the cluster profile (which includes the secret's name)
cpDetails, err := resolverClient.ClusterProfile(cp.profileName)
if err != nil {
return nil, fmt.Errorf("failed to retrieve details from config resolver for '%s' cluster cp", cp.profileName)
}
// Get the secret from the ci namespace. We expect it exists
ciSecret := &coreapi.Secret{}
err = client.Get(ctx, ctrlruntimeclient.ObjectKey{Namespace: "ci", Name: cpDetails.Secret}, ciSecret)
if err != nil {
return nil, fmt.Errorf("failed to get secret '%s' from ci namespace: %w", cpDetails.Secret, err)
}

newSecret := &coreapi.Secret{
Data: ciSecret.Data,
Type: ciSecret.Type,
ObjectMeta: meta.ObjectMeta{
Name: fmt.Sprintf("%s-cluster-profile", cp.target),
},
}

return newSecret, nil
}

type clusterProfileForTarget struct {
target string
profileName string
}

// getClusterProfileNamesFromTargets extracts the needed cluster profile name(s) from the target arg(s)
func (o *options) getClusterProfileNamesFromTargets() {
psalajova marked this conversation as resolved.
Show resolved Hide resolved
for _, targetName := range o.targets.values {
for _, test := range o.configSpec.Tests {
Copy link
Member

Choose a reason for hiding this comment

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

Consider having a helper method on the Tests object to retrieve the cluster profiles.

if targetName != test.As {
continue
}
profile := test.GetClusterProfileName()
if profile != "" {
o.clusterProfiles = append(o.clusterProfiles, clusterProfileForTarget{
target: test.As,
profileName: profile,
})
}
}
}
}
74 changes: 74 additions & 0 deletions cmd/ci-operator/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1615,3 +1615,77 @@ func TestHandleTargetAdditionalSuffix(t *testing.T) {
})
}
}

func TestGetClusterProfileNamesFromTargets(t *testing.T) {
testCases := []struct {
name string
options *options
expectedProfileNames []string
}{
{
name: "single target, single profile",
options: &options{
targets: stringSlice{values: []string{"target-test-1"}},
configSpec: &api.ReleaseBuildConfiguration{
Tests: []api.TestStepConfiguration{
{
As: "target-test-1",
MultiStageTestConfigurationLiteral: &api.MultiStageTestConfigurationLiteral{
ClusterProfile: "profile1",
},
},
},
},
},
expectedProfileNames: []string{"profile1"},
},
{
name: "multiple targets, multiple profiles",
options: &options{
targets: stringSlice{values: []string{"target-test-1", "target-test-2"}},
configSpec: &api.ReleaseBuildConfiguration{
Tests: []api.TestStepConfiguration{
{
As: "target-test-1",
MultiStageTestConfigurationLiteral: &api.MultiStageTestConfigurationLiteral{
ClusterProfile: "profile1",
},
},
{
As: "target-test-2",
MultiStageTestConfigurationLiteral: &api.MultiStageTestConfigurationLiteral{
ClusterProfile: "profile2",
},
},
},
},
},
expectedProfileNames: []string{"profile1", "profile2"},
},
{
name: "target without cluster profile",
options: &options{
targets: stringSlice{values: []string{"target-test-1"}},
configSpec: &api.ReleaseBuildConfiguration{
Tests: []api.TestStepConfiguration{
{
As: "target-test-1",
},
{
As: "target-test-2",
MultiStageTestConfigurationLiteral: &api.MultiStageTestConfigurationLiteral{},
},
},
},
},
expectedProfileNames: nil,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.options.getClusterProfileNamesFromTargets()
reflect.DeepEqual(tc.expectedProfileNames, tc.options.clusterProfiles)
})
}
}
13 changes: 13 additions & 0 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,19 @@ func (config TestStepConfiguration) IsPeriodic() bool {
return config.Interval != nil || config.MinimumInterval != nil || config.Cron != nil || config.ReleaseController
}

// GetClusterProfileName returns the cluster profile name if it's set
func (config TestStepConfiguration) GetClusterProfileName() string {
switch {
case config.MultiStageTestConfigurationLiteral != nil:
return config.MultiStageTestConfigurationLiteral.ClusterProfile.Name()
case config.MultiStageTestConfiguration != nil:
return config.MultiStageTestConfiguration.ClusterProfile.Name()
case config.OpenshiftInstallerClusterTestConfiguration != nil:
return config.OpenshiftInstallerClusterTestConfiguration.ClusterProfile.Name()
}
return ""
}

// Cloud is the name of a cloud provider, e.g., aws cluster topology, etc.
type Cloud string

Expand Down