diff --git a/data/data/install.openshift.io_installconfigs.yaml b/data/data/install.openshift.io_installconfigs.yaml index 4b19f98f0f7..813b99d5d2f 100644 --- a/data/data/install.openshift.io_installconfigs.yaml +++ b/data/data/install.openshift.io_installconfigs.yaml @@ -2229,6 +2229,11 @@ spec: description: Network specifies an existing VPC where the cluster should be created rather than provisioning a new one. type: string + networkProjectID: + description: NetworkProjectID is currently unsupported. NetworkProjectID + specifies which project the network and subnets exist in when + they are not in the main ProjectID. + type: string projectID: description: ProjectID is the the project that will be used for the cluster. diff --git a/pkg/asset/installconfig/gcp/validation.go b/pkg/asset/installconfig/gcp/validation.go index 9138dff3077..3ffb20e153c 100644 --- a/pkg/asset/installconfig/gcp/validation.go +++ b/pkg/asset/installconfig/gcp/validation.go @@ -42,6 +42,7 @@ func Validate(client API, ic *types.InstallConfig) error { } allErrs = append(allErrs, validateProject(client, ic, field.NewPath("platform").Child("gcp"))...) + allErrs = append(allErrs, validateNetworkProject(client, ic, field.NewPath("platform").Child("gcp"))...) allErrs = append(allErrs, validateRegion(client, ic, field.NewPath("platform").Child("gcp"))...) allErrs = append(allErrs, validateNetworks(client, ic, field.NewPath("platform").Child("gcp"))...) allErrs = append(allErrs, validateInstanceTypes(client, ic)...) @@ -162,17 +163,38 @@ func validateProject(client API, ic *types.InstallConfig, fieldPath *field.Path) return allErrs } +func validateNetworkProject(client API, ic *types.InstallConfig, fieldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if ic.GCP.NetworkProjectID != "" { + projects, err := client.GetProjects(context.TODO()) + if err != nil { + return append(allErrs, field.InternalError(fieldPath.Child("networkProjectID"), err)) + } + if _, found := projects[ic.GCP.NetworkProjectID]; !found { + return append(allErrs, field.Invalid(fieldPath.Child("networkProjectID"), ic.GCP.NetworkProjectID, "invalid project ID")) + } + } + + return allErrs +} + // validateNetworks checks that the user-provided VPC is in the project and the provided subnets are valid. func validateNetworks(client API, ic *types.InstallConfig, fieldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} + networkProjectID := ic.GCP.NetworkProjectID + if networkProjectID == "" { + networkProjectID = ic.GCP.ProjectID + } + if ic.GCP.Network != "" { - _, err := client.GetNetwork(context.TODO(), ic.GCP.Network, ic.GCP.ProjectID) + _, err := client.GetNetwork(context.TODO(), ic.GCP.Network, networkProjectID) if err != nil { return append(allErrs, field.Invalid(fieldPath.Child("network"), ic.GCP.Network, err.Error())) } - subnets, err := client.GetSubnetworks(context.TODO(), ic.GCP.Network, ic.GCP.ProjectID, ic.GCP.Region) + subnets, err := client.GetSubnetworks(context.TODO(), ic.GCP.Network, networkProjectID, ic.GCP.Region) if err != nil { return append(allErrs, field.Invalid(fieldPath.Child("network"), ic.GCP.Network, "failed to retrieve subnets")) } diff --git a/pkg/asset/installconfig/gcp/validation_test.go b/pkg/asset/installconfig/gcp/validation_test.go index 882ffc0c1bf..607ec3dec10 100644 --- a/pkg/asset/installconfig/gcp/validation_test.go +++ b/pkg/asset/installconfig/gcp/validation_test.go @@ -60,14 +60,15 @@ var ( ic.Platform.GCP.DefaultMachinePlatform.InstanceType = "n1-dne-1" } - invalidateNetwork = func(ic *types.InstallConfig) { ic.GCP.Network = "invalid-vpc" } - invalidateComputeSubnet = func(ic *types.InstallConfig) { ic.GCP.ComputeSubnet = "invalid-compute-subnet" } - invalidateCPSubnet = func(ic *types.InstallConfig) { ic.GCP.ControlPlaneSubnet = "invalid-cp-subnet" } - invalidateRegion = func(ic *types.InstallConfig) { ic.GCP.Region = invalidRegion } - invalidateProject = func(ic *types.InstallConfig) { ic.GCP.ProjectID = invalidProjectName } - removeVPC = func(ic *types.InstallConfig) { ic.GCP.Network = "" } - removeSubnets = func(ic *types.InstallConfig) { ic.GCP.ComputeSubnet, ic.GCP.ControlPlaneSubnet = "", "" } - invalidClusterName = func(ic *types.InstallConfig) { ic.ObjectMeta.Name = "testgoogletest" } + invalidateNetwork = func(ic *types.InstallConfig) { ic.GCP.Network = "invalid-vpc" } + invalidateComputeSubnet = func(ic *types.InstallConfig) { ic.GCP.ComputeSubnet = "invalid-compute-subnet" } + invalidateCPSubnet = func(ic *types.InstallConfig) { ic.GCP.ControlPlaneSubnet = "invalid-cp-subnet" } + invalidateRegion = func(ic *types.InstallConfig) { ic.GCP.Region = invalidRegion } + invalidateProject = func(ic *types.InstallConfig) { ic.GCP.ProjectID = invalidProjectName } + invalidateNetworkProject = func(ic *types.InstallConfig) { ic.GCP.NetworkProjectID = invalidProjectName } + removeVPC = func(ic *types.InstallConfig) { ic.GCP.Network = "" } + removeSubnets = func(ic *types.InstallConfig) { ic.GCP.ComputeSubnet, ic.GCP.ControlPlaneSubnet = "", "" } + invalidClusterName = func(ic *types.InstallConfig) { ic.ObjectMeta.Name = "testgoogletest" } machineTypeAPIResult = map[string]*compute.MachineType{ "n1-standard-1": {GuestCpus: 1, MemoryMb: 3840}, @@ -229,6 +230,12 @@ func TestGCPInstallConfigValidation(t *testing.T) { expectedError: true, expectedErrMsg: "platform.gcp.project: Invalid value: \"invalid-project\": invalid project ID", }, + { + name: "Invalid network project ID", + edits: editFunctions{invalidateNetworkProject}, + expectedError: true, + expectedErrMsg: "platform.gcp.networkProjectID: Invalid value: \"invalid-project\": invalid project ID", + }, { name: "Valid Region", edits: editFunctions{}, diff --git a/pkg/types/gcp/platform.go b/pkg/types/gcp/platform.go index 01ece511a0d..217c51bc3cb 100644 --- a/pkg/types/gcp/platform.go +++ b/pkg/types/gcp/platform.go @@ -20,6 +20,12 @@ type Platform struct { // +optional Network string `json:"network,omitempty"` + // NetworkProjectID is currently unsupported. + // NetworkProjectID specifies which project the network and subnets exist in when + // they are not in the main ProjectID. + // +optional + NetworkProjectID string `json:"networkProjectID,omitempty"` + // ControlPlaneSubnet is an existing subnet where the control plane will be deployed. // The value should be the name of the subnet. // +optional