pkg/infrastructure/authorizationCheckers/ARMTemplateDeployment/armTemplateAuthorizationChecker.go (128 lines of code) (raw):

// MIT License // // Copyright (c) Microsoft Corporation. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE package ARMTemplateDeployment import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "time" // "log" "net/http" "strings" "github.com/Azure/mpf/pkg/infrastructure/ARMTemplateShared" "github.com/Azure/mpf/pkg/infrastructure/azureAPI" "github.com/Azure/mpf/pkg/infrastructure/mpfSharedUtils" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" "github.com/Azure/mpf/pkg/domain" log "github.com/sirupsen/logrus" ) type armDeploymentConfig struct { ctx context.Context armConfig ARMTemplateShared.ArmTemplateAdditionalConfig azAPIClient *azureAPI.AzureAPIClients } func NewARMTemplateDeploymentAuthorizationChecker(subscriptionID string, armConfig ARMTemplateShared.ArmTemplateAdditionalConfig) *armDeploymentConfig { azAPIClient := azureAPI.NewAzureAPIClients(subscriptionID) return &armDeploymentConfig{ azAPIClient: azAPIClient, armConfig: armConfig, ctx: context.Background(), } } func (a *armDeploymentConfig) GetDeploymentAuthorizationErrors(mpfConfig domain.MPFConfig) (string, error) { return a.deployARMTemplate(a.armConfig.DeploymentName, mpfConfig) } func (a *armDeploymentConfig) CleanDeployment(mpfConfig domain.MPFConfig) error { log.Infoln("Cleaning up resources...") log.Infoln("*************************") // Cancel deployment. Even if cancelling deployment fails attempt to delete other resources _ = a.cancelDeployment(a.ctx, a.armConfig.DeploymentName, mpfConfig) return nil } func (a *armDeploymentConfig) deployARMTemplate(deploymentName string, mpfConfig domain.MPFConfig) (string, error) { // jsonData, err := json.Marshal(properties) // spCred, err := azidentity.NewClientSecretCredential(a.mpfCfg.Args.TenantID, a.mpfCfg.Args.SPClientID, a.mpfCfg.Args.SPClientSecret, nil) // if err != nil { // log.Fatal(err) // } bearerToken, err := a.azAPIClient.GetSPBearerToken(mpfConfig.TenantID, mpfConfig.SP.SPClientID, mpfConfig.SP.SPClientSecret) if err != nil { return "", err } // read template and parameters template, err := mpfSharedUtils.ReadJson(a.armConfig.TemplateFilePath) if err != nil { return "", err } parameters, err := mpfSharedUtils.ReadJson(a.armConfig.ParametersFilePath) if err != nil { return "", err } // convert parameters to standard format parameters = ARMTemplateShared.GetParametersInStandardFormat(parameters) fullTemplate := map[string]interface{}{ "properties": map[string]interface{}{ "mode": "Incremental", "template": template, "parameters": parameters, }, } // convert bodyJSON to string fullTemplateJSONBytes, err := json.Marshal(fullTemplate) if err != nil { return "", err } fullTemplateJSONString := string(fullTemplateJSONBytes) log.Debugln() log.Debugln(fullTemplateJSONString) log.Debugln() // create JSON body with template and parameters client := &http.Client{} log.Info("MPF mode is fullDeployment, Proceeding to create resources....") url := fmt.Sprintf("https://management.azure.com/subscriptions/%s/resourcegroups/%s/providers/Microsoft.Resources/deployments/%s?api-version=2020-10-01", mpfConfig.SubscriptionID, mpfConfig.ResourceGroup.ResourceGroupName, deploymentName) reqMethod := "PUT" req, err := http.NewRequest(reqMethod, url, bytes.NewBufferString(fullTemplateJSONString)) if err != nil { return "", err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") req.Header.Set("User-Agent", "Go HTTP Client") // add bearer token to header req.Header.Add("Authorization", "Bearer "+bearerToken) // make request resp, err := client.Do(req) if err != nil { return "", err } defer resp.Body.Close() var respBody string // read response body body, err := io.ReadAll(resp.Body) if err != nil { return "", err } respBody = string(body) // fmt.Println(respBody) log.Debugln(respBody) // print response body if strings.Contains(respBody, "Authorization") { return respBody, nil } if strings.Contains(respBody, "InvalidTemplateDeployment") { // This indicates all Authorization errors are fixed // Sample error [{\"code\":\"PodIdentityAddonFeatureFlagNotEnabled\",\"message\":\"Provisioning of resource(s) for container service aks-24xalwx7i2ueg in resource group testdeployrg-Y2jsRAG failed. Message: PodIdentity addon is not allowed since feature 'Microsoft.ContainerService/EnablePodIdentityPreview' is not enabled. // Hence ok to proceed, and not return error in this condition log.Warnf("Non Authorizaton error occured: %s", respBody) } return "", nil } // func (a *armDeploymentConfig) getARMDeployment(deploymentName string) error { // bearerToken, err := a.azAPIClient.GetSPBearerToken(a.mpfCfg.TenantID, a.mpfCfg.SPClientID, a.mpfCfg.SPClientSecret) // if err != nil { // return err // } // url := fmt.Sprintf("https://management.azure.com/subscriptions/%s/resourcegroups/%s/providers/Microsoft.Resources/deployments/%s?api-version=2020-10-01", a.mpfCfg.SubscriptionID, a.mpfCfg.ResourceGroup.ResourceGroupName, deploymentName) // client := &http.Client{} // req, err := http.NewRequest("GET", url, nil) // if err != nil { // return err // } // req.Header.Set("Content-Type", "application/json") // req.Header.Set("Accept", "application/json") // req.Header.Set("User-Agent", "Go HTTP Client") // // add bearer token to header // req.Header.Add("Authorization", "Bearer "+bearerToken) // // make request // resp, err := client.Do(req) // if err != nil { // return err // } // defer resp.Body.Close() // // read response body // body, err := io.ReadAll(resp.Body) // if err != nil { // return err // } // respBody := string(body) // // fmt.Println(respBody) // log.Debugln(respBody) // // print response body // if strings.Contains(respBody, "Authorization") { // return errors.New(respBody) // } // return nil // } // Delete ARM deployment func (a *armDeploymentConfig) cancelDeployment(ctx context.Context, deploymentName string, mpfConfig domain.MPFConfig) error { // Get deployments status. If status is "Running", cancel deployment, then delete deployment getResp, err := a.azAPIClient.DeploymentsClient.Get(ctx, mpfConfig.ResourceGroup.ResourceGroupName, deploymentName, nil) if err != nil { // Error indicates deployment does not exist, so cancelling deployment not needed if strings.Contains(err.Error(), "DeploymentNotFound") { log.Infof("Could not get deployment %s: ,Error :%s \n", deploymentName, err) return nil } } log.Infof("Deployment status: %s\n", *getResp.DeploymentExtended.Properties.ProvisioningState) if *getResp.DeploymentExtended.Properties.ProvisioningState == armresources.ProvisioningStateRunning { retryCount := 0 for _, err := a.azAPIClient.DeploymentsClient.Cancel(ctx, mpfConfig.ResourceGroup.ResourceGroupName, deploymentName, nil); err != nil; { // cancel deployment if err != nil { // return err log.Warnf("Could not cancel deployment %s: %s, retrying in a bit", deploymentName, err) time.Sleep(5 * time.Second) retryCount++ if retryCount >= 24 { log.Warnf("Could not cancel deployment %s: %s, giving up", deploymentName, err) return errors.New("could not cancel deployment") } } } log.Infof("Cancelled deployment %s", deploymentName) return nil } return nil }