in pkg/util/cluster/cluster.go [429:632]
func (c *Cluster) Create(ctx context.Context) error {
c.log.Info("Creating cluster")
clusterGet, err := c.openshiftclusters.Get(ctx, c.Config.VnetResourceGroup, c.Config.ClusterName)
c.log.Info("Got cluster ref")
if err == nil {
if clusterGet.Properties.ProvisioningState == api.ProvisioningStateFailed {
return fmt.Errorf("cluster exists and is in failed provisioning state, please delete and retry: %s, %s", clusterGet.ID, c.Config.VnetResourceGroup)
}
c.log.Print("cluster already exists, skipping create")
return nil
}
appDetails := appDetails{}
if !c.Config.UseWorkloadIdentity {
c.log.Info("Creating app")
appDetails, err = c.createApp(ctx, c.Config.ClusterName)
if err != nil {
return err
}
}
visibility := api.VisibilityPublic
if c.Config.IsPrivate || c.Config.NoInternet {
visibility = api.VisibilityPrivate
}
if c.Config.IsCI {
c.log.Infof("creating resource group")
_, err = c.groups.CreateOrUpdate(ctx, c.Config.VnetResourceGroup, mgmtfeatures.ResourceGroup{
Location: to.StringPtr(c.Config.Location),
})
if err != nil {
return err
}
}
asset, err := assets.EmbeddedFiles.ReadFile(generator.FileClusterPredeploy)
if err != nil {
return err
}
var template map[string]interface{}
err = json.Unmarshal(asset, &template)
if err != nil {
return err
}
addressPrefix, masterSubnet, workerSubnet, err := c.generateSubnets()
if err != nil {
return err
}
diskEncryptionSetName := fmt.Sprintf(
"%s%s",
c.Config.VnetResourceGroup,
generator.SharedDiskEncryptionSetNameSuffix,
)
var kvName string
if !c.Config.IsCI {
if len(c.Config.VnetResourceGroup) > 10 {
// keyvault names need to have a maximum length of 24,
// so we need to cut off some chars if the resource group name is too long
kvName = c.Config.VnetResourceGroup[:10] + generator.SharedDiskEncryptionKeyVaultNameSuffix
} else {
kvName = c.Config.VnetResourceGroup + generator.SharedDiskEncryptionKeyVaultNameSuffix
}
} else {
// if DES already exists in RG, then reuse KV hosting the key of this DES,
// otherwise, name is limited to 24 characters, but must be globally unique,
// so we generate a name randomly until it is available
diskEncryptionSet, err := c.diskEncryptionSetsClient.Get(ctx, c.Config.VnetResourceGroup, diskEncryptionSetName)
if err == nil {
if diskEncryptionSet.EncryptionSetProperties == nil ||
diskEncryptionSet.EncryptionSetProperties.ActiveKey == nil ||
diskEncryptionSet.EncryptionSetProperties.ActiveKey.SourceVault == nil ||
diskEncryptionSet.EncryptionSetProperties.ActiveKey.SourceVault.ID == nil {
return fmt.Errorf("no valid Key Vault found in Disk Encryption Set: %v. Delete the Disk Encryption Set and retry", diskEncryptionSet)
}
ID := *diskEncryptionSet.EncryptionSetProperties.ActiveKey.SourceVault.ID
var found bool
_, kvName, found = strings.Cut(ID, "/providers/Microsoft.KeyVault/vaults/")
if !found {
return fmt.Errorf("could not find Key Vault name in ID: %v", ID)
}
} else {
if autorestErr, ok := err.(autorest.DetailedError); !ok ||
autorestErr.Response == nil ||
autorestErr.Response.StatusCode != http.StatusNotFound {
return fmt.Errorf("failed to get Disk Encryption Set: %v", err)
}
for {
kvName = "kv-" + uuid.DefaultGenerator.Generate()[:21]
result, err := c.vaultsClient.CheckNameAvailability(
ctx,
sdkkeyvault.VaultCheckNameAvailabilityParameters{Name: &kvName, Type: to.StringPtr("Microsoft.KeyVault/vaults")},
nil,
)
if err != nil {
return err
}
if result.NameAvailable == nil {
return fmt.Errorf("have unexpected nil NameAvailable for key vault: %v", kvName)
}
if *result.NameAvailable {
break
}
c.log.Infof("key vault %v is not available and we will try an other one", kvName)
}
}
}
parameters := map[string]*arm.ParametersParameter{
"clusterName": {Value: c.Config.ClusterName},
"ci": {Value: c.Config.IsCI},
"vnetAddressPrefix": {Value: addressPrefix},
"masterAddressPrefix": {Value: masterSubnet},
"workerAddressPrefix": {Value: workerSubnet},
"kvName": {Value: kvName},
}
// TODO: ick
if os.Getenv("NO_INTERNET") != "" {
parameters["routes"] = &arm.ParametersParameter{
Value: []sdknetwork.Route{
{
Properties: &sdknetwork.RoutePropertiesFormat{
AddressPrefix: pointerutils.ToPtr("0.0.0.0/0"),
NextHopType: pointerutils.ToPtr(sdknetwork.RouteNextHopTypeNone),
},
Name: pointerutils.ToPtr("blackhole"),
},
},
}
}
armctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
defer cancel()
c.log.Info("predeploying ARM template")
err = c.deployments.CreateOrUpdateAndWait(armctx, c.Config.VnetResourceGroup, c.Config.ClusterName, mgmtfeatures.Deployment{
Properties: &mgmtfeatures.DeploymentProperties{
Template: template,
Parameters: parameters,
Mode: mgmtfeatures.Incremental,
},
})
if err != nil {
return err
}
diskEncryptionSetID := fmt.Sprintf(
"/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/diskEncryptionSets/%s",
c.Config.SubscriptionID,
c.Config.VnetResourceGroup,
diskEncryptionSetName,
)
if c.Config.UseWorkloadIdentity {
c.log.Info("creating WIs")
if err := c.SetupWorkloadIdentity(ctx, c.Config.VnetResourceGroup); err != nil {
return fmt.Errorf("error setting up Workload Identity Roles: %w", err)
}
} else {
c.log.Info("creating Classic role assignments")
c.SetupServicePrincipalRoleAssignments(ctx, diskEncryptionSetID, appDetails.SPId)
}
fipsMode := true
// Don't install with FIPS in a local dev, non-CI environment
if !c.Config.IsCI && c.Config.IsLocalDevelopmentMode() {
fipsMode = false
}
c.log.Info("creating cluster")
err = c.createCluster(ctx, c.Config.VnetResourceGroup, c.Config.ClusterName, appDetails.applicationId, appDetails.applicationSecret, diskEncryptionSetID, visibility, c.Config.OSClusterVersion, fipsMode)
if err != nil {
return err
}
if c.Config.IsCI {
c.log.Info("fixing up NSGs")
err = c.fixupNSGs(ctx, c.Config.VnetResourceGroup, c.Config.ClusterName)
if err != nil {
return err
}
if env.IsLocalDevelopmentMode() {
c.log.Info("peering subnets to CI infra")
err = c.peerSubnetsToCI(ctx, c.Config.VnetResourceGroup)
if err != nil {
return err
}
}
}
c.log.Info("done")
return nil
}