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
42 changes: 33 additions & 9 deletions pkg/asset/installconfig/gcp/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -836,11 +836,21 @@ func validateMarketplaceImages(client API, ic *types.InstallConfig) field.ErrorL
var defaultImage *compute.Image
var defaultOsImage *gcp.OSImage

// Check if this is a sovereign cloud installation
isSovereignCloud := gcp.GetCloudEnvironment(ic.GCP.ProjectID) == gcp.CloudEnvironmentSovereign

if ic.GCP.DefaultMachinePlatform != nil && ic.GCP.DefaultMachinePlatform.OSImage != nil {
defaultOsImage = ic.GCP.DefaultMachinePlatform.OSImage
defaultImage, err = client.GetImage(context.TODO(), defaultOsImage.Name, defaultOsImage.Project)
if err != nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("platform", "gcp", "defaultMachinePlatform", "osImage"), *defaultOsImage, fmt.Sprintf(errorMessage, err)))
if isSovereignCloud && defaultOsImage.Project == "" {
allErrs = append(allErrs, field.Required(
field.NewPath("platform", "gcp", "defaultMachinePlatform", "osImage", "project"),
"must specify image project for sovereign cloud"))
}
if defaultOsImage.Project != "" {
defaultImage, err = client.GetImage(context.TODO(), defaultOsImage.Name, defaultOsImage.Project)
if err != nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("platform", "gcp", "defaultMachinePlatform", "osImage"), *defaultOsImage, fmt.Sprintf(errorMessage, err)))
}
}
}

Expand All @@ -849,9 +859,16 @@ func validateMarketplaceImages(client API, ic *types.InstallConfig) field.ErrorL
osImage := defaultOsImage
if ic.ControlPlane.Platform.GCP != nil && ic.ControlPlane.Platform.GCP.OSImage != nil {
osImage = ic.ControlPlane.Platform.GCP.OSImage
image, err = client.GetImage(context.TODO(), osImage.Name, osImage.Project)
if err != nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("controlPlane", "platform", "gcp", "osImage"), *osImage, fmt.Sprintf(errorMessage, err)))
if isSovereignCloud && osImage.Project == "" {
allErrs = append(allErrs, field.Required(
field.NewPath("controlPlane", "platform", "gcp", "osImage", "project"),
"must specify image project for sovereign cloud"))
}
if osImage.Project != "" {
image, err = client.GetImage(context.TODO(), osImage.Name, osImage.Project)
if err != nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("controlPlane", "platform", "gcp", "osImage"), *osImage, fmt.Sprintf(errorMessage, err)))
}
}
}
if image != nil {
Expand All @@ -867,9 +884,16 @@ func validateMarketplaceImages(client API, ic *types.InstallConfig) field.ErrorL
fieldPath := field.NewPath("compute").Index(idx)
if compute.Platform.GCP != nil && compute.Platform.GCP.OSImage != nil {
osImage = compute.Platform.GCP.OSImage
image, err = client.GetImage(context.TODO(), osImage.Name, osImage.Project)
if err != nil {
allErrs = append(allErrs, field.Invalid(fieldPath.Child("platform", "gcp", "osImage"), *osImage, fmt.Sprintf(errorMessage, err)))
if isSovereignCloud && osImage.Project == "" {
allErrs = append(allErrs, field.Required(
fieldPath.Child("platform", "gcp", "osImage", "project"),
"must specify image project for sovereign cloud"))
}
if osImage.Project != "" {
image, err = client.GetImage(context.TODO(), osImage.Name, osImage.Project)
if err != nil {
allErrs = append(allErrs, field.Invalid(fieldPath.Child("platform", "gcp", "osImage"), *osImage, fmt.Sprintf(errorMessage, err)))
}
}
}
if image != nil {
Expand Down
71 changes: 71 additions & 0 deletions pkg/asset/installconfig/gcp/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1547,6 +1547,44 @@ func TestValidateMarketplaceImages(t *testing.T) {
ic.ControlPlane.Platform.GCP.OSImage.Project = projectID
}

// Sovereign cloud test helpers
sovereignCloudWithDefaultImageNoProject = func(ic *types.InstallConfig) {
ic.Platform.GCP.ProjectID = "eu0:sovereign-project"
ic.Platform.GCP.DefaultMachinePlatform.OSImage = &gcp.OSImage{
Name: validImage,
Project: "",
}
}
sovereignCloudWithDefaultImageWithProject = func(ic *types.InstallConfig) {
ic.Platform.GCP.ProjectID = "eu0:sovereign-project"
ic.Platform.GCP.DefaultMachinePlatform.OSImage = &gcp.OSImage{
Name: validImage,
Project: projectID,
}
}
sovereignCloudWithControlPlaneImageNoProject = func(ic *types.InstallConfig) {
ic.Platform.GCP.ProjectID = "eu0:sovereign-project"
ic.ControlPlane.Platform.GCP.OSImage = &gcp.OSImage{
Name: validImage,
Project: "",
}
}
sovereignCloudWithComputeImageNoProject = func(ic *types.InstallConfig) {
ic.Platform.GCP.ProjectID = "eu0:sovereign-project"
ic.Compute[0].Platform.GCP.OSImage = &gcp.OSImage{
Name: validImage,
Project: "",
}
}
sovereignCloudNoDefaultImage = func(ic *types.InstallConfig) {
ic.Platform.GCP.ProjectID = "eu0:sovereign-project"
ic.Platform.GCP.DefaultMachinePlatform.OSImage = nil
}
sovereignCloudNilDefaultMachinePlatform = func(ic *types.InstallConfig) {
ic.Platform.GCP.ProjectID = "eu0:sovereign-project"
ic.Platform.GCP.DefaultMachinePlatform = nil
}

marketplaceImageAPIResult = &compute.Image{
Architecture: "X86_64",
}
Expand Down Expand Up @@ -1633,6 +1671,39 @@ func TestValidateMarketplaceImages(t *testing.T) {
expectedError: false,
expectedWarnMsg: "Boot image architecture is unspecified and might not be compatible with amd64 controlPlane nodes",
},
{
name: "Sovereign cloud without osImage",
edits: editFunctions{sovereignCloudNoDefaultImage},
expectedError: false,
},
{
name: "Sovereign cloud with osImage but no project in defaultMachinePlatform",
edits: editFunctions{sovereignCloudWithDefaultImageNoProject},
expectedError: true,
expectedErrMsg: `^\[platform.gcp.defaultMachinePlatform.osImage.project: Required value: must specify image project for sovereign cloud\]$`,
},
{
name: "Sovereign cloud with osImage and project in defaultMachinePlatform",
edits: editFunctions{sovereignCloudWithDefaultImageWithProject},
expectedError: false,
},
{
name: "Sovereign cloud with osImage but no project in controlPlane",
edits: editFunctions{sovereignCloudWithControlPlaneImageNoProject, sovereignCloudNoDefaultImage},
expectedError: true,
expectedErrMsg: `^\[controlPlane.platform.gcp.osImage.project: Required value: must specify image project for sovereign cloud\]$`,
},
{
name: "Sovereign cloud with osImage but no project in compute",
edits: editFunctions{sovereignCloudWithComputeImageNoProject, sovereignCloudNoDefaultImage},
expectedError: true,
expectedErrMsg: `^\[compute\[0\].platform.gcp.osImage.project: Required value: must specify image project for sovereign cloud\]$`,
},
{
name: "Sovereign cloud with nil DefaultMachinePlatform",
edits: editFunctions{sovereignCloudNilDefaultMachinePlatform},
expectedError: false,
},
}

mockCtrl := gomock.NewController(t)
Expand Down
9 changes: 7 additions & 2 deletions pkg/asset/machines/clusterapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,11 +338,16 @@ func (c *ClusterAPI) Generate(ctx context.Context, dependencies asset.Parents) e
}
pool.Platform.GCP = &mpool

controlPlaneImage := rhcosImage.ControlPlane
if gcptypes.NeedsRHCOSUpload(ic.Platform.GCP.ProjectID, &mpool) {
controlPlaneImage = gcptypes.RHCOSImageRef(ic.Platform.GCP.ProjectID, clusterID.InfraID)
}

gcpMachines, err := gcp.GenerateMachines(
installConfig,
clusterID.InfraID,
&pool,
rhcosImage.ControlPlane,
controlPlaneImage,
)
if err != nil {
return fmt.Errorf("failed to create master machine objects %w", err)
Expand All @@ -355,7 +360,7 @@ func (c *ClusterAPI) Generate(ctx context.Context, dependencies asset.Parents) e
installConfig,
clusterID.InfraID,
&pool,
rhcosImage.ControlPlane,
controlPlaneImage,
)
if err != nil {
return fmt.Errorf("failed to create bootstrap machine objects %w", err)
Expand Down
6 changes: 5 additions & 1 deletion pkg/asset/machines/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,11 @@ func (m *Master) Generate(ctx context.Context, dependencies asset.Parents) error
mpool.Zones = azs
}
pool.Platform.GCP = &mpool
machines, controlPlaneMachineSet, err = gcp.Machines(clusterID.InfraID, ic, &pool, rhcosImage.ControlPlane, "master", masterUserDataSecretName)
controlPlaneImage := rhcosImage.ControlPlane
if gcptypes.NeedsRHCOSUpload(ic.Platform.GCP.ProjectID, &mpool) {
controlPlaneImage = gcptypes.RHCOSImageRef(ic.Platform.GCP.ProjectID, clusterID.InfraID)
}
machines, controlPlaneMachineSet, err = gcp.Machines(clusterID.InfraID, ic, &pool, controlPlaneImage, "master", masterUserDataSecretName)
if err != nil {
return errors.Wrap(err, "failed to create master machine objects")
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/asset/machines/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,11 @@ func (w *Worker) Generate(ctx context.Context, dependencies asset.Parents) error
mpool.Zones = azs
}
pool.Platform.GCP = &mpool
sets, err := gcp.MachineSets(clusterID.InfraID, ic, &pool, rhcosImage.Compute, "worker", workerUserDataSecretName)
computeImage := rhcosImage.Compute
if gcptypes.NeedsRHCOSUpload(ic.Platform.GCP.ProjectID, &mpool) {
computeImage = gcptypes.RHCOSImageRef(ic.Platform.GCP.ProjectID, clusterID.InfraID)
}
sets, err := gcp.MachineSets(clusterID.InfraID, ic, &pool, computeImage, "worker", workerUserDataSecretName)
if err != nil {
return errors.Wrap(err, "failed to create worker machine objects")
}
Expand Down
10 changes: 10 additions & 0 deletions pkg/asset/rhcos/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ func osImage(ctx context.Context, ic *installconfig.InstallConfig, machinePool *
}
return osimage, nil
case gcp.Name:
// For sovereign clouds, the pre-built images in rhcos-cloud are not
// accessible. Return the download URL so that PreProvision can upload
// a cluster-specific image.
if gcp.GetCloudEnvironment(ic.Config.Platform.GCP.ProjectID) == gcp.CloudEnvironmentSovereign {
if a, ok := streamArch.Artifacts["gcp"]; ok {
return rhcos.FindArtifactURL(a)
}
return "", fmt.Errorf("%s: No GCP artifact found for image upload", streamArchPrefix)
}
if streamArch.Images.Gcp != nil {
img := streamArch.Images.Gcp
return fmt.Sprintf("projects/%s/global/images/%s", img.Project, img.Name), nil
Expand Down Expand Up @@ -252,6 +261,7 @@ func MakeAsset(osImage string) *Image {
}
}


func getHyperVGeneration(metadata *icazure.Metadata, role string) (string, error) {
if role == types.MachinePoolControlPlaneRoleName {
return metadata.ControlPlaneHyperVGeneration()
Expand Down
10 changes: 10 additions & 0 deletions pkg/infrastructure/gcp/clusterapi/clusterapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ func (p Provider) PreProvision(ctx context.Context, in clusterapi.PreProvisionIn
}
}

// Upload a cluster-specific RHCOS image for sovereign clouds where the
// pre-built images in the rhcos-cloud project are not accessible.
// Machine manifests are already populated with the deterministic image
// reference during generation.
if gcptypes.NeedsRHCOSUpload(projectID, controlPlaneMpool) {
if _, err := uploadRHCOSImage(ctx, in); err != nil {
return fmt.Errorf("failed to upload RHCOS image: %w", err)
}
}
Comment on lines +105 to +113

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Upload when any resolved machine pool needs it.

This only checks controlPlaneMpool. With a custom control-plane osImage and a compute pool using the default image, worker manifests reference <infraID>-rhcos but this path skips creating it. Evaluate the resolved compute pools too before deciding to upload.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/infrastructure/gcp/clusterapi/clusterapi.go` around lines 105 - 113,
Update the RHCOS upload decision in the cluster creation flow around
NeedsRHCOSUpload and uploadRHCOSImage to evaluate all resolved compute machine
pools, not only controlPlaneMpool. Trigger the upload when any control-plane or
compute pool requires the cluster-specific image, while preserving the existing
upload error propagation.


return nil
}

Expand Down
Loading