Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dry Run and tests #878

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
45 changes: 45 additions & 0 deletions tooling/templatize/pkg/config/types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package config

import "testing"

func TestGetByPath(t *testing.T) {
tests := []struct {
name string
vars Variables
path string
want any
found bool
}{
{
name: "simple",
vars: Variables{
"key": "value",
},
path: "key",
want: "value",
found: true,
},
{
name: "nested",
vars: Variables{
"key": Variables{
"key": "value",
},
},
path: "key.key",
want: "value",
found: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, found := tt.vars.GetByPath(tt.path)
if got != tt.want {
t.Errorf("Variables.GetByPath() got = %v, want %v", got, tt.want)
}
if found != tt.found {
t.Errorf("Variables.GetByPath() found = %v, want %v", found, tt.found)
}
})
}
}
6 changes: 4 additions & 2 deletions tooling/templatize/pkg/ev2/pipeline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ func TestPrecompilePipelineForEV2(t *testing.T) {
}
fmt.Println(p)
expectedParamsPath := "ev2-precompiled-test.bicepparam"
if p.ResourceGroups[0].Steps[1].Parameters != expectedParamsPath {
t.Errorf("expected parameters path %v, but got %v", expectedParamsPath, p.ResourceGroups[0].Steps[1].Parameters)

armStep := p.ResourceGroups[0].Steps[2]
if armStep.Parameters != expectedParamsPath {
t.Errorf("expected parameters path %v, but got %v", expectedParamsPath, armStep.Parameters)
}
// TODO improve test, check against fixture
}
2 changes: 1 addition & 1 deletion tooling/templatize/pkg/pipeline/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func inspectVars(s *step, options *PipelineInspectOptions, writer io.Writer) err
var err error
switch s.Action {
case "Shell":
envVars, err = s.getEnvVars(options.Vars, false)
envVars, err = s.mapStepVariables(options.Vars)
default:
return fmt.Errorf("inspecting step variables not implemented for action type %s", s.Action)
}
Expand Down
41 changes: 39 additions & 2 deletions tooling/templatize/pkg/pipeline/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,19 @@ func (rg *resourceGroup) run(ctx context.Context, options *PipelineRunOptions) e
}

logger := logr.FromContextOrDiscard(ctx)

kubeconfigFile, err := prepareKubeConfig(ctx, executionTarget)
if kubeconfigFile != "" {
defer func() {
if err := os.Remove(kubeconfigFile); err != nil {
logger.V(5).Error(err, "failed to delete kubeconfig file", "kubeconfig", kubeconfigFile)
}
}()
}
if err != nil {
return fmt.Errorf("failed to prepare kubeconfig: %w", err)
}

for _, step := range rg.Steps {
if options.Step != "" && step.Name != options.Step {
// skip steps that don't match the specified step name
Expand All @@ -109,6 +122,7 @@ func (rg *resourceGroup) run(ctx context.Context, options *PipelineRunOptions) e
"aksCluster", executionTarget.AKSClusterName,
),
),
kubeconfigFile,
executionTarget, options,
)
if err != nil {
Expand All @@ -118,20 +132,43 @@ func (rg *resourceGroup) run(ctx context.Context, options *PipelineRunOptions) e
return nil
}

func (s *step) run(ctx context.Context, executionTarget *ExecutionTarget, options *PipelineRunOptions) error {
func (s *step) run(ctx context.Context, kubeconfigFile string, executionTarget *ExecutionTarget, options *PipelineRunOptions) error {
fmt.Println("\n---------------------")
if options.DryRun {
fmt.Println("This is a dry run!")
}
fmt.Println(s.description())
fmt.Print("\n")

switch s.Action {
case "Shell":
return s.runShellStep(ctx, executionTarget, options)
return s.runShellStep(ctx, kubeconfigFile, options)
case "ARM":
return s.runArmStep(ctx, executionTarget, options)
default:
return fmt.Errorf("unsupported action type %q", s.Action)
}
}

func prepareKubeConfig(ctx context.Context, executionTarget *ExecutionTarget) (string, error) {
logger := logr.FromContextOrDiscard(ctx)
kubeconfigFile := ""
if executionTarget.AKSClusterName != "" {
logger.V(5).Info("Building kubeconfig for AKS cluster")
kubeconfigFile, err := executionTarget.KubeConfig(ctx)
if err != nil {
return "", fmt.Errorf("failed to build kubeconfig for %s: %w", executionTarget.aksID(), err)
}
defer func() {
if err := os.Remove(kubeconfigFile); err != nil {
logger.V(5).Error(err, "failed to delete kubeconfig file", "kubeconfig", kubeconfigFile)
}
}()
logger.V(5).Info("kubeconfig set to shell execution environment", "kubeconfig", kubeconfigFile)
}
return kubeconfigFile, nil
}

func (s *step) description() string {
var details []string
switch s.Action {
Expand Down
88 changes: 44 additions & 44 deletions tooling/templatize/pkg/pipeline/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package pipeline
import (
"context"
"fmt"
"os"
"maps"
"os/exec"

"github.com/go-logr/logr"
Expand All @@ -12,75 +12,75 @@ import (
"github.com/Azure/ARO-HCP/tooling/templatize/pkg/utils"
)

func (s *step) runShellStep(ctx context.Context, executionTarget *ExecutionTarget, options *PipelineRunOptions) error {
func (s *step) createCommand(ctx context.Context, dryRun bool, envVars map[string]string) (*exec.Cmd, bool) {
var cmd *exec.Cmd
if dryRun {
if s.DryRun.Command == nil && s.DryRun.EnvVars == nil {
return nil, true
}
for _, e := range s.DryRun.EnvVars {
envVars[e.Name] = e.Value
}
if s.DryRun.Command != nil {
cmd = exec.CommandContext(ctx, s.DryRun.Command[0], s.DryRun.Command[1:]...)
}
}
if cmd == nil {
// if dry-run is not enabled, use the actual command or also if no dry-run command is defined
cmd = exec.CommandContext(ctx, s.Command[0], s.Command[1:]...)
}
cmd.Env = append(cmd.Env, utils.MapToEnvVarArray(envVars)...)
return cmd, false
}

func (s *step) runShellStep(ctx context.Context, kubeconfigFile string, options *PipelineRunOptions) error {
if s.outputFunc == nil {
s.outputFunc = func(output string) {
fmt.Println(output)
}
}

logger := logr.FromContextOrDiscard(ctx)

// build ENV vars
envVars, err := s.getEnvVars(options.Vars, true)
stepVars, err := s.mapStepVariables(options.Vars)
if err != nil {
return fmt.Errorf("failed to build env vars: %w", err)
}

// prepare kubeconfig
if executionTarget.AKSClusterName != "" {
logger.V(5).Info("Building kubeconfig for AKS cluster")
kubeconfigFile, err := executionTarget.KubeConfig(ctx)
if err != nil {
return fmt.Errorf("failed to build kubeconfig for %s: %w", executionTarget.aksID(), err)
}
defer func() {
if err := os.Remove(kubeconfigFile); err != nil {
logger.V(5).Error(err, "failed to delete kubeconfig file", "kubeconfig", kubeconfigFile)
}
}()
envVars["KUBECONFIG"] = kubeconfigFile
logger.V(5).Info("kubeconfig set to shell execution environment", "kubeconfig", kubeconfigFile)
envVars := utils.GetOsVariable()

maps.Copy(envVars, stepVars)
// execute the command
cmd, skipCommand := s.createCommand(ctx, options.DryRun, envVars)
if skipCommand {
logger.V(5).Info("Skipping step '%s' due to missing dry-run configuiration", s.Name)
return nil
}

// TODO handle dry-run
if kubeconfigFile != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("KUBECONFIG=%s", kubeconfigFile))
}

// execute the command
logger.V(5).Info(fmt.Sprintf("Executing shell command: %s\n", s.Command), "command", s.Command)
cmd := exec.CommandContext(ctx, s.Command[0], s.Command[1:]...)
cmd.Env = append(cmd.Env, utils.MapToEnvVarArray(envVars)...)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to execute shell command: %s %w", string(output), err)
}

// print the output of the command
fmt.Println(string(output))
s.outputFunc(string(output))

return nil
}

func (s *step) getEnvVars(vars config.Variables, includeOSEnvVars bool) (map[string]string, error) {
func (s *step) mapStepVariables(vars config.Variables) (map[string]string, error) {
envVars := make(map[string]string)
envVars["RUNS_IN_TEMPLATIZE"] = "1"
if includeOSEnvVars {
for k, v := range utils.GetOSEnvVarsAsMap() {
envVars[k] = v
}
}
for _, e := range s.Env {
value, found := vars.GetByPath(e.ConfigRef)
if !found {
return nil, fmt.Errorf("failed to lookup config reference %s for %s", e.ConfigRef, e.Name)
}
envVars[e.Name] = anyToString(value)
envVars[e.Name] = utils.AnyToString(value)
}
return envVars, nil
}

func anyToString(value any) string {
switch v := value.(type) {
case string:
return v
case int:
return fmt.Sprintf("%d", v)
case bool:
return fmt.Sprintf("%t", v)
default:
return fmt.Sprintf("%v", v)
}
}
Loading
Loading