Skip to content

Commit

Permalink
Merge pull request #994 from Azure/add-arm-dry-run
Browse files Browse the repository at this point in the history
Add ARM deploy what if
  • Loading branch information
janboll authored Dec 17, 2024
2 parents 8a39c9f + 4f00e4d commit b4810f7
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 26 deletions.
103 changes: 93 additions & 10 deletions tooling/templatize/pkg/pipeline/arm.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package pipeline
import (
"context"
"fmt"
"strings"

"github.com/Azure/ARO-HCP/tooling/templatize/pkg/config"

"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
Expand All @@ -29,35 +32,116 @@ func newArmClient(subscriptionID, region string) *armClient {
}

func (a *armClient) runArmStep(ctx context.Context, options *PipelineRunOptions, rgName string, step *Step, input map[string]output) (output, error) {
// Ensure resourcegroup exists
err := a.ensureResourceGroupExists(ctx, rgName, options.NoPersist)
if err != nil {
return nil, fmt.Errorf("failed to ensure resource group exists: %w", err)
}

// Run deployment
client, err := armresources.NewDeploymentsClient(a.SubscriptionID, a.creds, nil)
if err != nil {
return nil, fmt.Errorf("failed to create deployments client: %w", err)
}
if options.DryRun {
return doDryRun(ctx, client, rgName, step, options.Vars, input)
}

return doWaitForDeployment(ctx, client, rgName, step, options.Vars, input)
}

func recursivePrint(level int, change *armresources.WhatIfPropertyChange) {
fmt.Printf("%s%s:\n", strings.Repeat("\t", level), *change.Path)
fmt.Printf("%s\tBefore:%s\n", strings.Repeat("\t", level), change.Before)
fmt.Printf("%s\tAfter:%s\n", strings.Repeat("\t", level), change.After)
for _, child := range change.Children {
level += level
recursivePrint(level, child)
}
}
func printChanges(t armresources.ChangeType, changes []*armresources.WhatIfChange) {
for _, change := range changes {
if *change.ChangeType == t {
fmt.Printf("%s %s\n", strings.Repeat("\t", 1), *change.ResourceID)
for _, delta := range change.Delta {
recursivePrint(2, delta)
}
}
}
}
func doDryRun(ctx context.Context, client *armresources.DeploymentsClient, rgName string, step *Step, vars config.Variables, input map[string]output) (output, error) {

logger := logr.FromContextOrDiscard(ctx)

inputValues, err := getInputValues(step.Variables, input)
if err != nil {
return nil, fmt.Errorf("failed to get input values: %w", err)
}
// Transform Bicep to ARM
deploymentProperties, err := transformBicepToARM(ctx, step.Parameters, options.Vars, inputValues)
deploymentProperties, err := transformBicepToARMWhatIfDeployment(ctx, step.Parameters, vars, inputValues)
if err != nil {
return nil, fmt.Errorf("failed to transform Bicep to ARM: %w", err)
}

// Create the deployment
deployment := armresources.Deployment{
deployment := armresources.DeploymentWhatIf{
Properties: deploymentProperties,
}

// Ensure resourcegroup exists
err = a.ensureResourceGroupExists(ctx, rgName, options.NoPersist)
poller, err := client.BeginWhatIf(ctx, rgName, step.Name, deployment, nil)
if err != nil {
return nil, fmt.Errorf("failed to ensure resource group exists: %w", err)
return nil, fmt.Errorf("failed to create WhatIf Deployment: %w", err)
}
logger.Info("WhatIf Deployment started", "deployment", step.Name)

// TODO handle dry-run
resp, err := poller.PollUntilDone(ctx, nil)
if err != nil {
return nil, fmt.Errorf("failed to wait for deployment completion: %w", err)
}
logger.Info("WhatIf Deployment finished successfully", "deployment", step.Name)

fmt.Println("Change report for WhatIf deployment")
fmt.Println("----------")
fmt.Println("Creating")
printChanges(armresources.ChangeTypeCreate, resp.Properties.Changes)
fmt.Println("----------")
fmt.Println("Deploy")
printChanges(armresources.ChangeTypeDeploy, resp.Properties.Changes)
fmt.Println("----------")
fmt.Println("Modify")
printChanges(armresources.ChangeTypeModify, resp.Properties.Changes)
fmt.Println("----------")
fmt.Println("Delete")
printChanges(armresources.ChangeTypeDelete, resp.Properties.Changes)
fmt.Println("----------")
fmt.Println("Ignoring")
printChanges(armresources.ChangeTypeIgnore, resp.Properties.Changes)
fmt.Println("----------")
fmt.Println("NoChange")
printChanges(armresources.ChangeTypeNoChange, resp.Properties.Changes)
fmt.Println("----------")
fmt.Println("Unsupported")
printChanges(armresources.ChangeTypeUnsupported, resp.Properties.Changes)

// Run deployment
client, err := armresources.NewDeploymentsClient(a.SubscriptionID, a.creds, nil)
return nil, nil
}

func doWaitForDeployment(ctx context.Context, client *armresources.DeploymentsClient, rgName string, step *Step, vars config.Variables, input map[string]output) (output, error) {
logger := logr.FromContextOrDiscard(ctx)

inputValues, err := getInputValues(step.Variables, input)
if err != nil {
return nil, fmt.Errorf("failed to create deployments client: %w", err)
return nil, fmt.Errorf("failed to get input values: %w", err)
}
// Transform Bicep to ARM
deploymentProperties, err := transformBicepToARMDeployment(ctx, step.Parameters, vars, inputValues)
if err != nil {
return nil, fmt.Errorf("failed to transform Bicep to ARM: %w", err)
}

// Create the deployment
deployment := armresources.Deployment{
Properties: deploymentProperties,
}

poller, err := client.BeginCreateOrUpdate(ctx, rgName, step.Name, deployment, nil)
Expand All @@ -66,7 +150,6 @@ func (a *armClient) runArmStep(ctx context.Context, options *PipelineRunOptions,
}
logger.Info("Deployment started", "deployment", step.Name)

// Wait for completion
resp, err := poller.PollUntilDone(ctx, nil)
if err != nil {
return nil, fmt.Errorf("failed to wait for deployment completion: %w", err)
Expand Down
48 changes: 32 additions & 16 deletions tooling/templatize/pkg/pipeline/bicep.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,31 @@ import (
"github.com/Azure/ARO-HCP/tooling/templatize/pkg/config"
)

func transformBicepToARM(ctx context.Context, bicepParameterTemplateFile string, vars config.Variables, inputs map[string]any) (*armresources.DeploymentProperties, error) {
// preprocess bicep parameter file and store it
func transformBicepToARMWhatIfDeployment(ctx context.Context, bicepParameterTemplateFile string, vars config.Variables, inputs map[string]any) (*armresources.DeploymentWhatIfProperties, error) {
template, params, err := transformParameters(ctx, vars, inputs, bicepParameterTemplateFile)
if err != nil {
return nil, err
}
return &armresources.DeploymentWhatIfProperties{
Mode: to.Ptr(armresources.DeploymentModeIncremental),
Template: template,
Parameters: params,
}, nil
}

func transformBicepToARMDeployment(ctx context.Context, bicepParameterTemplateFile string, vars config.Variables, inputs map[string]any) (*armresources.DeploymentProperties, error) {
template, params, err := transformParameters(ctx, vars, inputs, bicepParameterTemplateFile)
if err != nil {
return nil, err
}
return &armresources.DeploymentProperties{
Mode: to.Ptr(armresources.DeploymentModeIncremental),
Template: template,
Parameters: params,
}, nil
}

func transformParameters(ctx context.Context, vars config.Variables, inputs map[string]any, bicepParameterTemplateFile string) (map[string]interface{}, map[string]interface{}, error) {
combinedVars := make(map[string]any)
for k, v := range vars {
combinedVars[k] = v
Expand All @@ -27,45 +49,39 @@ func transformBicepToARM(ctx context.Context, bicepParameterTemplateFile string,

bicepParamContent, err := config.PreprocessFile(bicepParameterTemplateFile, combinedVars)
if err != nil {
return nil, fmt.Errorf("failed to preprocess file: %w", err)
return nil, nil, fmt.Errorf("failed to preprocess file: %w", err)
}
bicepParamBaseDir := filepath.Dir(bicepParameterTemplateFile)
bicepParamFile, err := os.CreateTemp(bicepParamBaseDir, "bicep-params-*.bicepparam")
if err != nil {
return nil, fmt.Errorf("failed to create temp file: %w", err)
return nil, nil, fmt.Errorf("failed to create temp file: %w", err)
}
defer os.Remove(bicepParamFile.Name())
_, err = bicepParamFile.Write(bicepParamContent)
if err != nil {
return nil, fmt.Errorf("failed to write to target file: %w", err)
return nil, nil, fmt.Errorf("failed to write to target file: %w", err)
}

// transform to json
cmd := exec.CommandContext(ctx, "az", "bicep", "build-params", "-f", bicepParamFile.Name(), "--stdout")
output, err := cmd.Output()
if err != nil {
combinedOutput, _ := cmd.CombinedOutput()
return nil, fmt.Errorf("failed to get output from command: %w\n%s", err, string(combinedOutput))
return nil, nil, fmt.Errorf("failed to get output from command: %w\n%s", err, string(combinedOutput))
}

// parse json and build DeploymentProperties
var result generationResult
if err := json.Unmarshal(output, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal output: %w", err)
return nil, nil, fmt.Errorf("failed to unmarshal output: %w", err)
}
template, err := result.Template()
if err != nil {
return nil, fmt.Errorf("failed to get template: %w", err)
return nil, nil, fmt.Errorf("failed to get template: %w", err)
}
params, err := result.Parameters()
if err != nil {
return nil, fmt.Errorf("failed to get parameters: %w", err)
return nil, nil, fmt.Errorf("failed to get parameters: %w", err)
}
return &armresources.DeploymentProperties{
Mode: to.Ptr(armresources.DeploymentModeIncremental),
Template: template,
Parameters: params,
}, nil
return template, params, nil
}

type generationResult struct {
Expand Down

0 comments on commit b4810f7

Please sign in to comment.