From c5eaffc89d91eed297f71613e27c2abe0991facf Mon Sep 17 00:00:00 2001 From: David Gamero Date: Mon, 6 May 2024 17:16:35 -0400 Subject: [PATCH 1/3] gomodule multistage build (#274) --- template/dockerfiles/gomodule/Dockerfile | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/template/dockerfiles/gomodule/Dockerfile b/template/dockerfiles/gomodule/Dockerfile index b6b8a059..2ee0e11f 100644 --- a/template/dockerfiles/gomodule/Dockerfile +++ b/template/dockerfiles/gomodule/Dockerfile @@ -1,12 +1,14 @@ -FROM golang:{{VERSION}} +FROM golang:{{VERSION}} AS builder ENV PORT {{PORT}} EXPOSE {{PORT}} -WORKDIR /go/src/app +WORKDIR /build +COPY go.mod go.sum ./ +RUN go mod download && go mod verify COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -v -o app-binary -RUN go get -RUN go build -v -o app -RUN mv ./app /go/bin/ - -CMD ["app"] \ No newline at end of file +FROM gcr.io/distroless/static-debian12 +WORKDIR /app +COPY --from=builder /build/app-binary . +CMD ["/app/app-binary"] From 797503968c49e50e57712a30c5230b2741f44c7e Mon Sep 17 00:00:00 2001 From: Brandon Foley Date: Wed, 15 May 2024 11:22:53 -0400 Subject: [PATCH 2/3] A few updates to draft (#277) --- cmd/generate-workflow.go | 51 +++++++++++++- example/dockerfile_test.go | 4 +- pkg/addons/addons.go | 2 + pkg/config/draftconfig.go | 19 ++++-- pkg/deployments/deployments.go | 2 + pkg/languages/languages.go | 2 + pkg/prompts/prompts.go | 11 ++- pkg/prompts/prompts_test.go | 6 +- pkg/workflows/workflows.go | 67 +++++-------------- pkg/workflows/workflows_test.go | 14 ++-- .../addons/azure/webapp_routing/draft.yaml | 6 ++ .../addons/azure/webapp_routing/ingress.yaml | 2 + .../helm/charts/templates/deployment.yaml | 1 + .../helm/charts/templates/namespace.yaml | 1 + .../helm/charts/templates/service.yaml | 1 + template/deployments/helm/draft.yaml | 5 ++ .../kustomize/base/deployment.yaml | 1 + .../deployments/kustomize/base/namespace.yaml | 4 +- .../deployments/kustomize/base/service.yaml | 2 + template/deployments/kustomize/draft.yaml | 5 ++ .../overlays/production/deployment.yaml | 1 + .../overlays/production/service.yaml | 2 + template/deployments/manifests/draft.yaml | 5 ++ .../manifests/manifests/deployment.yaml | 1 + .../manifests/manifests/service.yaml | 2 + test/templates/deployment.yaml | 1 + .../helm/charts/templates/deployment.yaml | 1 + .../helm/charts/templates/namespace.yaml | 1 + .../helm/charts/templates/service.yaml | 1 + test/templates/invalid_deployment.yaml | 1 + test/templates/kustomize/base/deployment.yaml | 1 + test/templates/kustomize/base/service.yaml | 2 + .../overlays/production/deployment.yaml | 2 + .../overlays/production/service.yaml | 2 + .../manifests/manifests/deployment.yaml | 1 + .../manifests/manifests/service.yaml | 2 + test/templates/service_w_annotations.yaml | 2 + .../unsupported_no_of_containers.yaml | 1 + 38 files changed, 165 insertions(+), 70 deletions(-) diff --git a/cmd/generate-workflow.go b/cmd/generate-workflow.go index 452cb2fa..22c75f07 100644 --- a/cmd/generate-workflow.go +++ b/cmd/generate-workflow.go @@ -1,12 +1,19 @@ package cmd import ( + "fmt" + "strings" + + "github.com/manifoldco/promptui" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "golang.org/x/exp/maps" + "github.com/Azure/draft/pkg/prompts" "github.com/Azure/draft/pkg/templatewriter" "github.com/Azure/draft/pkg/templatewriter/writers" "github.com/Azure/draft/pkg/workflows" + "github.com/Azure/draft/template" ) type generateWorkflowCmd struct { @@ -34,7 +41,7 @@ with draft on AKS. This command assumes the 'setup-gh' command has been run prop flagValuesMap = gwCmd.workflowConfig.SetFlagValuesToMap() } log.Info("--> Generating Github workflow") - if err := workflows.CreateWorkflows(gwCmd.dest, gwCmd.deployType, gwCmd.flagVariables, gwCmd.templateWriter, flagValuesMap); err != nil { + if err := gwCmd.generateWorkflows(gwCmd.dest, gwCmd.deployType, gwCmd.flagVariables, gwCmd.templateWriter, flagValuesMap); err != nil { return err } @@ -61,3 +68,45 @@ with draft on AKS. This command assumes the 'setup-gh' command has been run prop func init() { rootCmd.AddCommand(newGenerateWorkflowCmd()) } + +func (gwc *generateWorkflowCmd) generateWorkflows(dest string, deployType string, flagVariables []string, templateWriter templatewriter.TemplateWriter, flagValuesMap map[string]string) error { + if flagValuesMap == nil { + return fmt.Errorf("flagValuesMap is nil") + } + var err error + for _, flagVar := range flagVariables { + flagVarName, flagVarValue, ok := strings.Cut(flagVar, "=") + if !ok { + return fmt.Errorf("invalid variable format: %s", flagVar) + } + flagValuesMap[flagVarName] = flagVarValue + log.Debugf("flag variable %s=%s", flagVarName, flagVarValue) + } + + if deployType == "" { + selection := &promptui.Select{ + Label: "Select k8s Deployment Type", + Items: []string{"helm", "kustomize", "manifests"}, + } + + _, deployType, err = selection.Run() + if err != nil { + return err + } + } + + workflow := workflows.CreateWorkflowsFromEmbedFS(template.Workflows, dest) + workflowConfig, err := workflow.GetConfig(deployType) + if err != nil { + return fmt.Errorf("get config: %w", err) + } + + customInputs, err := prompts.RunPromptsFromConfigWithSkips(workflowConfig, maps.Keys(flagValuesMap)) + if err != nil { + return err + } + + maps.Copy(customInputs, flagValuesMap) + + return workflow.CreateWorkflowFiles(deployType, customInputs, templateWriter) +} \ No newline at end of file diff --git a/example/dockerfile_test.go b/example/dockerfile_test.go index 624f9952..fd74a5c5 100644 --- a/example/dockerfile_test.go +++ b/example/dockerfile_test.go @@ -28,12 +28,12 @@ func TestWriteDockerfile(t *testing.T) { expectError: false, }, { - name: "Test Invalid Go Dockerfile Generation", + name: "Test Valid Go Dockerfile Generation with deafult", inputVariables: map[string]string{ "PORT": "8080", }, generationLanguage: "go", - expectError: true, + expectError: false, }, { name: "Test Invalid GenerationLanguage", diff --git a/pkg/addons/addons.go b/pkg/addons/addons.go index 17a39181..584e0d7f 100644 --- a/pkg/addons/addons.go +++ b/pkg/addons/addons.go @@ -39,6 +39,8 @@ func GenerateAddon(addons embed.FS, provider, addon, dest string, userInputs map return err } + addOnConfig.ApplyDefaultVariables(userInputs) + if err = osutil.CopyDir(addons, selectedAddonPath, addonDestPath, &addOnConfig.DraftConfig, userInputs, templateWriter); err != nil { return err } diff --git a/pkg/config/draftconfig.go b/pkg/config/draftconfig.go index 62e92869..05b53864 100644 --- a/pkg/config/draftconfig.go +++ b/pkg/config/draftconfig.go @@ -24,13 +24,13 @@ type BuilderVar struct { Description string `yaml:"description"` VarType string `yaml:"type"` ExampleValues []string `yaml:"exampleValues"` - IsPromptDisabled bool `yaml:"disablePrompt"` } type BuilderVarDefault struct { - Name string `yaml:"name"` - Value string `yaml:"value"` - ReferenceVar string `yaml:"referenceVar"` + Name string `yaml:"name"` + Value string `yaml:"value"` + ReferenceVar string `yaml:"referenceVar"` + IsPromptDisabled bool `yaml:"disablePrompt"` } func (d *DraftConfig) GetVariableExampleValues() map[string][]string { @@ -64,6 +64,17 @@ func (d *DraftConfig) GetNameOverride(path string) string { return prefix } +// ApplyDefaultVariables will apply the defaults to variables that are not already set +func (d *DraftConfig) ApplyDefaultVariables(customConfig map[string]string) { + for _, variable := range d.VariableDefaults { + // handle where variable is not set or is set to an empty string from cli handling + if defaultVal, ok := customConfig[variable.Name]; !ok || defaultVal == "" { + log.Infof("Variable %s defaulting to value %s", variable.Name, variable.Value) + customConfig[variable.Name] = variable.Value + } + } +} + // TemplateVariableRecorder is an interface for recording variables that are used read using draft configs type TemplateVariableRecorder interface { Record(key, value string) diff --git a/pkg/deployments/deployments.go b/pkg/deployments/deployments.go index 7d13ce71..f290e59c 100644 --- a/pkg/deployments/deployments.go +++ b/pkg/deployments/deployments.go @@ -46,6 +46,8 @@ func (d *Deployments) CopyDeploymentFiles(deployType string, customInputs map[st deployConfig, ok := d.configs[deployType] if !ok { deployConfig = nil + } else { + deployConfig.ApplyDefaultVariables(customInputs) } if err := osutil.CopyDir(d.deploymentTemplates, srcDir, d.dest, deployConfig, customInputs, templateWriter); err != nil { diff --git a/pkg/languages/languages.go b/pkg/languages/languages.go index 64369ad7..6ef62e0d 100644 --- a/pkg/languages/languages.go +++ b/pkg/languages/languages.go @@ -52,6 +52,8 @@ func (l *Languages) CreateDockerfileForLanguage(lang string, customInputs map[st draftConfig, ok := l.configs[lang] if !ok { draftConfig = nil + } else { + draftConfig.ApplyDefaultVariables(customInputs) } if err := osutil.CopyDir(l.dockerfileTemplates, srcDir, l.dest, draftConfig, customInputs, templateWriter); err != nil { diff --git a/pkg/prompts/prompts.go b/pkg/prompts/prompts.go index 80e68f97..125a6f3b 100644 --- a/pkg/prompts/prompts.go +++ b/pkg/prompts/prompts.go @@ -35,7 +35,7 @@ func RunPromptsFromConfigWithSkipsIO(config *config.DraftConfig, varsToSkip []st log.Debugf("Skipping prompt for %s", promptVariableName) continue } - if customPrompt.IsPromptDisabled { + if GetIsPromptDisabled(customPrompt.Name, config.VariableDefaults) { log.Debugf("Skipping prompt for %s as it has IsPromptDisabled=true", promptVariableName) noPromptDefaultValue := GetVariableDefaultValue(promptVariableName, config.VariableDefaults, inputs) if noPromptDefaultValue == "" { @@ -90,6 +90,15 @@ func GetVariableDefaultValue(variableName string, variableDefaults []config.Buil return defaultValue } +func GetIsPromptDisabled(variableName string, variableDefaults []config.BuilderVarDefault) bool { + for _, variableDefault := range variableDefaults { + if variableDefault.Name == variableName { + return variableDefault.IsPromptDisabled + } + } + return false +} + func RunBoolPrompt(customPrompt config.BuilderVar, Stdin io.ReadCloser, Stdout io.WriteCloser) (string, error) { newSelect := &promptui.Select{ Label: "Please select " + customPrompt.Description, diff --git a/pkg/prompts/prompts_test.go b/pkg/prompts/prompts_test.go index a0815693..689ead70 100644 --- a/pkg/prompts/prompts_test.go +++ b/pkg/prompts/prompts_test.go @@ -154,13 +154,13 @@ func TestRunPromptsFromConfigWithSkipsIO(t *testing.T) { { Name: "var1", Description: "var1 description", - IsPromptDisabled: true, }, }, VariableDefaults: []config.BuilderVarDefault{ { Name: "var1", Value: "defaultValue", + IsPromptDisabled: true, }, }, }, @@ -176,14 +176,12 @@ func TestRunPromptsFromConfigWithSkipsIO(t *testing.T) { { Name: "var1-no-prompt", Description: "var1 has IsPromptDisabled and should skip prompt and use default value", - IsPromptDisabled: true, }, { Name: "var2-default", Description: "var2 has a default value and will receive an empty value, so it should use the default value", }, { Name: "var3-no-prompt", Description: "var3 has IsPromptDisabled and should skip prompt and use default value", - IsPromptDisabled: true, }, { Name: "var4", Description: "var4 has a default value, but has a value entered, so it should use the entered value", @@ -193,12 +191,14 @@ func TestRunPromptsFromConfigWithSkipsIO(t *testing.T) { { Name: "var1-no-prompt", Value: "defaultValueNoPrompt1", + IsPromptDisabled: true, }, { Name: "var2-default", Value: "defaultValue2", }, { Name: "var3-no-prompt", Value: "defaultValueNoPrompt3", + IsPromptDisabled: true, }, { Name: "var4", Value: "defaultValue4", diff --git a/pkg/workflows/workflows.go b/pkg/workflows/workflows.go index c98d5b1d..1bc4b851 100644 --- a/pkg/workflows/workflows.go +++ b/pkg/workflows/workflows.go @@ -8,23 +8,18 @@ import ( "io/ioutil" "os" "path" - "strings" - "golang.org/x/exp/maps" "gopkg.in/yaml.v3" appsv1 "k8s.io/api/apps/v1" "k8s.io/cli-runtime/pkg/printers" "k8s.io/client-go/kubernetes/scheme" - "github.com/manifoldco/promptui" log "github.com/sirupsen/logrus" "github.com/Azure/draft/pkg/config" "github.com/Azure/draft/pkg/embedutils" "github.com/Azure/draft/pkg/osutil" - "github.com/Azure/draft/pkg/prompts" "github.com/Azure/draft/pkg/templatewriter" - "github.com/Azure/draft/template" ) const ( @@ -39,50 +34,6 @@ type Workflows struct { workflowTemplates fs.FS } -func CreateWorkflows(dest string, deployType string, flagVariables []string, templateWriter templatewriter.TemplateWriter, flagValuesMap map[string]string) error { - if flagValuesMap == nil { - return fmt.Errorf("flagValuesMap is nil") - } - var err error - for _, flagVar := range flagVariables { - flagVarName, flagVarValue, ok := strings.Cut(flagVar, "=") - if !ok { - return fmt.Errorf("invalid variable format: %s", flagVar) - } - flagValuesMap[flagVarName] = flagVarValue - log.Debugf("flag variable %s=%s", flagVarName, flagVarValue) - } - - if deployType == "" { - selection := &promptui.Select{ - Label: "Select k8s Deployment Type", - Items: []string{"helm", "kustomize", "manifests"}, - } - - _, deployType, err = selection.Run() - if err != nil { - return err - } - } - - workflow := createWorkflowsFromEmbedFS(template.Workflows, dest) - workflowConfig, ok := workflow.configs[deployType] - if !ok { - return errors.New("invalid deployment type") - } - customInputs, err := prompts.RunPromptsFromConfigWithSkips(workflowConfig, maps.Keys(flagValuesMap)) - if err != nil { - return err - } - - maps.Copy(customInputs, flagValuesMap) - - if err = updateProductionDeployments(deployType, dest, customInputs, templateWriter); err != nil { - return err - } - return workflow.createWorkflowFiles(deployType, customInputs, templateWriter) -} - func updateProductionDeployments(deployType, dest string, flagValuesMap map[string]string, templateWriter templatewriter.TemplateWriter) error { productionImage := fmt.Sprintf("%s.azurecr.io/%s", flagValuesMap["AZURECONTAINERREGISTRY"], flagValuesMap["CONTAINERNAME"]) switch deployType { @@ -176,7 +127,15 @@ func (w *Workflows) loadConfig(deployType string) (*config.DraftConfig, error) { return &draftConfig, nil } -func createWorkflowsFromEmbedFS(workflowTemplates embed.FS, dest string) *Workflows { +func (w *Workflows) GetConfig(deployType string) (*config.DraftConfig, error) { + val, ok := w.configs[deployType] + if !ok { + return nil, fmt.Errorf("deploy type %s unsupported", deployType) + } + return val, nil +} + +func CreateWorkflowsFromEmbedFS(workflowTemplates embed.FS, dest string) *Workflows { deployMap, err := embedutils.EmbedFStoMap(workflowTemplates, parentDirName) if err != nil { log.Fatal(err) @@ -204,7 +163,7 @@ func (w *Workflows) populateConfigs() { } } -func (w *Workflows) createWorkflowFiles(deployType string, customInputs map[string]string, templateWriter templatewriter.TemplateWriter) error { +func (w *Workflows) CreateWorkflowFiles(deployType string, customInputs map[string]string, templateWriter templatewriter.TemplateWriter) error { val, ok := w.workflows[deployType] if !ok { return fmt.Errorf("deployment type: %s is not currently supported", deployType) @@ -214,6 +173,12 @@ func (w *Workflows) createWorkflowFiles(deployType string, customInputs map[stri workflowConfig, ok := w.configs[deployType] if !ok { workflowConfig = nil + } else { + workflowConfig.ApplyDefaultVariables(customInputs) + } + + if err := updateProductionDeployments(deployType, w.dest, customInputs, templateWriter); err != nil { + return fmt.Errorf("update production deployments: %w", err) } if err := osutil.CopyDir(w.workflowTemplates, srcDir, w.dest, workflowConfig, customInputs, templateWriter); err != nil { diff --git a/pkg/workflows/workflows_test.go b/pkg/workflows/workflows_test.go index b514c38b..4da6eae1 100644 --- a/pkg/workflows/workflows_test.go +++ b/pkg/workflows/workflows_test.go @@ -24,7 +24,6 @@ import ( func TestCreateWorkflows(t *testing.T) { dest := "." deployType := "helm" - flagVariables := []string{} templatewriter := &writers.LocalFSWriter{} flagValuesMap := map[string]string{"AZURECONTAINERREGISTRY": "testAcr", "CONTAINERNAME": "testContainer", "RESOURCEGROUP": "testRG", "CLUSTERNAME": "testCluster", "BRANCHNAME": "testBranch", "BUILDCONTEXTPATH": "."} flagValuesMapNoRoot := map[string]string{"AZURECONTAINERREGISTRY": "testAcr", "CONTAINERNAME": "testContainer", "RESOURCEGROUP": "testRG", "CLUSTERNAME": "testCluster", "BRANCHNAME": "testBranch", "BUILDCONTEXTPATH": "test"} @@ -108,11 +107,12 @@ func TestCreateWorkflows(t *testing.T) { err := createTempDeploymentFile("charts", "charts/production.yaml", "../../test/templates/helm/charts/production.yaml") assert.Nil(t, err) - err = CreateWorkflows(dest, deployType, flagVariables, templatewriter, flagValuesMap) + workflows := CreateWorkflowsFromEmbedFS(template.Workflows, dest) + err = workflows.CreateWorkflowFiles(deployType, flagValuesMap, templatewriter) if err != nil { t.Errorf("Default Build Context CreateWorkflows() error = %v, wantErr %v", err, tt.shouldError) } - err = CreateWorkflows(dest, deployType, flagVariables, templatewriter, flagValuesMapNoRoot) + err = workflows.CreateWorkflowFiles(deployType, flagValuesMapNoRoot, templatewriter) if err != nil { t.Errorf("Custom Build Context CreateWorkflows() error = %v, wantErr %v", err, tt.shouldError) } @@ -248,18 +248,18 @@ func TestCreateWorkflowFiles(t *testing.T) { mockWF.populateConfigs() - err = mockWF.createWorkflowFiles("fakeDeployType", customInputs, templatewriter) + err = mockWF.CreateWorkflowFiles("fakeDeployType", customInputs, templatewriter) assert.NotNil(t, err) - err = mockWF.createWorkflowFiles("helm", customInputs, templatewriter) + err = mockWF.CreateWorkflowFiles("helm", customInputs, templatewriter) assert.Nil(t, err) os.RemoveAll(".github") - err = mockWF.createWorkflowFiles("helm", customInputsNoRoot, templatewriter) + err = mockWF.CreateWorkflowFiles("helm", customInputsNoRoot, templatewriter) assert.Nil(t, err) os.RemoveAll(".github") - err = mockWF.createWorkflowFiles("helm", badInputs, templatewriter) + err = mockWF.CreateWorkflowFiles("helm", badInputs, templatewriter) assert.NotNil(t, err) os.RemoveAll(".github") } diff --git a/template/addons/azure/webapp_routing/draft.yaml b/template/addons/azure/webapp_routing/draft.yaml index d7f4ba54..37361578 100644 --- a/template/addons/azure/webapp_routing/draft.yaml +++ b/template/addons/azure/webapp_routing/draft.yaml @@ -6,6 +6,12 @@ variables: type: "bool" - name: "ingress-host" description: "specify the host of the ingress resource" + - name: "GENERATORLABEL" + description: "the label to identify who generated the resource" +variableDefaults: + - name: "GENERATORLABEL" + value: "draft" + disablePrompt: true references: service: - name: "service-name" diff --git a/template/addons/azure/webapp_routing/ingress.yaml b/template/addons/azure/webapp_routing/ingress.yaml index 8475f12b..e2d32796 100644 --- a/template/addons/azure/webapp_routing/ingress.yaml +++ b/template/addons/azure/webapp_routing/ingress.yaml @@ -12,6 +12,8 @@ metadata: nginx.ingress.kubernetes.io/proxy-ssl-verify: "on" name: {{service-name}} namespace: {{service-namespace}} + labels: + kubernetes.azure.com/generator: {{GENERATORLABEL}} spec: ingressClassName: webapprouting.kubernetes.azure.com rules: diff --git a/template/deployments/helm/charts/templates/deployment.yaml b/template/deployments/helm/charts/templates/deployment.yaml index 43f53664..e1b38e17 100644 --- a/template/deployments/helm/charts/templates/deployment.yaml +++ b/template/deployments/helm/charts/templates/deployment.yaml @@ -4,6 +4,7 @@ metadata: name: {{ include "{{APPNAME}}.fullname" . }} labels: {{- include "{{APPNAME}}.labels" . | nindent 4 }} + kubernetes.azure.com/generator: {{GENERATORLABEL}} namespace: {{ .Values.namespace }} spec: {{- if not .Values.autoscaling.enabled }} diff --git a/template/deployments/helm/charts/templates/namespace.yaml b/template/deployments/helm/charts/templates/namespace.yaml index 01ee6b02..2e1b27f8 100644 --- a/template/deployments/helm/charts/templates/namespace.yaml +++ b/template/deployments/helm/charts/templates/namespace.yaml @@ -5,6 +5,7 @@ metadata: labels: {{- include "{{APPNAME}}.labels" . | nindent 4 }} openservicemesh.io/monitored-by: osm + kubernetes.azure.com/generator: {{GENERATORLABEL}} annotations: openservicemesh.io/sidecar-injection: enabled diff --git a/template/deployments/helm/charts/templates/service.yaml b/template/deployments/helm/charts/templates/service.yaml index dacd50ec..9ef0293f 100644 --- a/template/deployments/helm/charts/templates/service.yaml +++ b/template/deployments/helm/charts/templates/service.yaml @@ -4,6 +4,7 @@ metadata: name: {{ include "{{APPNAME}}.fullname" . }} labels: {{- include "{{APPNAME}}.labels" . | nindent 4 }} + kubernetes.azure.com/generator: {{GENERATORLABEL}} annotations: {{ toYaml .Values.service.annotations | nindent 4 }} namespace: {{ .Values.namespace }} diff --git a/template/deployments/helm/draft.yaml b/template/deployments/helm/draft.yaml index 446a2d53..fe569d50 100644 --- a/template/deployments/helm/draft.yaml +++ b/template/deployments/helm/draft.yaml @@ -11,6 +11,8 @@ variables: description: "the name of the image to use in the deployment" - name: "IMAGETAG" description: "the tag of the image to use in the deployment" + - name: "GENERATORLABEL" + description: "the label to identify who generated the resource" variableDefaults: - name: "PORT" value: 80 @@ -23,3 +25,6 @@ variableDefaults: - name: "IMAGETAG" value: "latest" disablePrompt: true + - name: "GENERATORLABEL" + value: "draft" + disablePrompt: true diff --git a/template/deployments/kustomize/base/deployment.yaml b/template/deployments/kustomize/base/deployment.yaml index 73b741c2..9b406898 100644 --- a/template/deployments/kustomize/base/deployment.yaml +++ b/template/deployments/kustomize/base/deployment.yaml @@ -4,6 +4,7 @@ metadata: name: {{APPNAME}} labels: app: {{APPNAME}} + kubernetes.azure.com/generator: {{GENERATORLABEL}} namespace: {{NAMESPACE}} spec: replicas: 1 diff --git a/template/deployments/kustomize/base/namespace.yaml b/template/deployments/kustomize/base/namespace.yaml index 18fa45f2..c0c9c3f5 100644 --- a/template/deployments/kustomize/base/namespace.yaml +++ b/template/deployments/kustomize/base/namespace.yaml @@ -1,4 +1,6 @@ kind: Namespace apiVersion: v1 metadata: - name: {{NAMESPACE}} \ No newline at end of file + name: {{NAMESPACE}} + labels: + kubernetes.azure.com/generator: {{GENERATORLABEL}} \ No newline at end of file diff --git a/template/deployments/kustomize/base/service.yaml b/template/deployments/kustomize/base/service.yaml index 3899e4d1..e99c7f41 100644 --- a/template/deployments/kustomize/base/service.yaml +++ b/template/deployments/kustomize/base/service.yaml @@ -3,6 +3,8 @@ kind: Service metadata: name: {{APPNAME}} namespace: {{NAMESPACE}} + labels: + kubernetes.azure.com/generator: {{GENERATORLABEL}} spec: type: LoadBalancer selector: diff --git a/template/deployments/kustomize/draft.yaml b/template/deployments/kustomize/draft.yaml index 29b01fc0..487550d9 100644 --- a/template/deployments/kustomize/draft.yaml +++ b/template/deployments/kustomize/draft.yaml @@ -11,6 +11,8 @@ variables: description: "the name of the image to use in the deployment" - name: "IMAGETAG" description: "the tag of the image to use in the deployment" + - name: "GENERATORLABEL" + description: "the label to identify who generated the resource" variableDefaults: - name: "PORT" value: 80 @@ -22,4 +24,7 @@ variableDefaults: referenceVar: "APPNAME" - name: "IMAGETAG" value: "latest" + disablePrompt: true + - name: "GENERATORLABEL" + value: "draft" disablePrompt: true \ No newline at end of file diff --git a/template/deployments/kustomize/overlays/production/deployment.yaml b/template/deployments/kustomize/overlays/production/deployment.yaml index de9bd2e6..6d93337e 100644 --- a/template/deployments/kustomize/overlays/production/deployment.yaml +++ b/template/deployments/kustomize/overlays/production/deployment.yaml @@ -4,6 +4,7 @@ metadata: name: {{APPNAME}} labels: app: {{APPNAME}} + kubernetes.azure.com/generator: {{GENERATORLABEL}} namespace: {{NAMESPACE}} spec: selector: diff --git a/template/deployments/kustomize/overlays/production/service.yaml b/template/deployments/kustomize/overlays/production/service.yaml index be5eecfa..7200c7b7 100644 --- a/template/deployments/kustomize/overlays/production/service.yaml +++ b/template/deployments/kustomize/overlays/production/service.yaml @@ -3,5 +3,7 @@ kind: Service metadata: name: {{APPNAME}} namespace: {{NAMESPACE}} + labels: + kubernetes.azure.com/generator: {{GENERATORLABEL}} spec: type: LoadBalancer diff --git a/template/deployments/manifests/draft.yaml b/template/deployments/manifests/draft.yaml index 29b01fc0..487550d9 100644 --- a/template/deployments/manifests/draft.yaml +++ b/template/deployments/manifests/draft.yaml @@ -11,6 +11,8 @@ variables: description: "the name of the image to use in the deployment" - name: "IMAGETAG" description: "the tag of the image to use in the deployment" + - name: "GENERATORLABEL" + description: "the label to identify who generated the resource" variableDefaults: - name: "PORT" value: 80 @@ -22,4 +24,7 @@ variableDefaults: referenceVar: "APPNAME" - name: "IMAGETAG" value: "latest" + disablePrompt: true + - name: "GENERATORLABEL" + value: "draft" disablePrompt: true \ No newline at end of file diff --git a/template/deployments/manifests/manifests/deployment.yaml b/template/deployments/manifests/manifests/deployment.yaml index 73b741c2..9b406898 100644 --- a/template/deployments/manifests/manifests/deployment.yaml +++ b/template/deployments/manifests/manifests/deployment.yaml @@ -4,6 +4,7 @@ metadata: name: {{APPNAME}} labels: app: {{APPNAME}} + kubernetes.azure.com/generator: {{GENERATORLABEL}} namespace: {{NAMESPACE}} spec: replicas: 1 diff --git a/template/deployments/manifests/manifests/service.yaml b/template/deployments/manifests/manifests/service.yaml index 3899e4d1..e99c7f41 100644 --- a/template/deployments/manifests/manifests/service.yaml +++ b/template/deployments/manifests/manifests/service.yaml @@ -3,6 +3,8 @@ kind: Service metadata: name: {{APPNAME}} namespace: {{NAMESPACE}} + labels: + kubernetes.azure.com/generator: {{GENERATORLABEL}} spec: type: LoadBalancer selector: diff --git a/test/templates/deployment.yaml b/test/templates/deployment.yaml index fdff1af9..69882ef1 100644 --- a/test/templates/deployment.yaml +++ b/test/templates/deployment.yaml @@ -4,6 +4,7 @@ metadata: name: my-app labels: app: my-app + kubernetes.azure.com/generator: draft spec: replicas: 1 selector: diff --git a/test/templates/helm/charts/templates/deployment.yaml b/test/templates/helm/charts/templates/deployment.yaml index 60352418..28b5b763 100644 --- a/test/templates/helm/charts/templates/deployment.yaml +++ b/test/templates/helm/charts/templates/deployment.yaml @@ -4,6 +4,7 @@ metadata: name: {{ include "test.fullname" . }} labels: {{- include "test.labels" . | nindent 4 }} + kubernetes.azure.com/generator: draft spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} diff --git a/test/templates/helm/charts/templates/namespace.yaml b/test/templates/helm/charts/templates/namespace.yaml index 296ce3d8..9bb651eb 100644 --- a/test/templates/helm/charts/templates/namespace.yaml +++ b/test/templates/helm/charts/templates/namespace.yaml @@ -5,6 +5,7 @@ metadata: labels: {{- include "test.labels" . | nindent 4 }} openservicemesh.io/monitored-by: osm + kubernetes.azure.com/generator: draft annotations: openservicemesh.io/sidecar-injection: enabled diff --git a/test/templates/helm/charts/templates/service.yaml b/test/templates/helm/charts/templates/service.yaml index 12a3116f..ba0da37e 100644 --- a/test/templates/helm/charts/templates/service.yaml +++ b/test/templates/helm/charts/templates/service.yaml @@ -4,6 +4,7 @@ metadata: name: {{ include "test.fullname" . }} labels: {{- include "test.labels" . | nindent 4 }} + kubernetes.azure.com/generator: draft annotations: {{ toYaml .Values.service.annotations | nindent 4 }} spec: diff --git a/test/templates/invalid_deployment.yaml b/test/templates/invalid_deployment.yaml index 01495d55..dae05428 100644 --- a/test/templates/invalid_deployment.yaml +++ b/test/templates/invalid_deployment.yaml @@ -4,6 +4,7 @@ metadata: name: my-app labels: app: my-app + kubernetes.azure.com/generator: draft spec: replicas: 1 selector: diff --git a/test/templates/kustomize/base/deployment.yaml b/test/templates/kustomize/base/deployment.yaml index 70be64e4..d79e1876 100644 --- a/test/templates/kustomize/base/deployment.yaml +++ b/test/templates/kustomize/base/deployment.yaml @@ -4,6 +4,7 @@ metadata: name: test labels: app: test + kubernetes.azure.com/generator: draft spec: replicas: 1 selector: diff --git a/test/templates/kustomize/base/service.yaml b/test/templates/kustomize/base/service.yaml index f6610fff..f97af43e 100644 --- a/test/templates/kustomize/base/service.yaml +++ b/test/templates/kustomize/base/service.yaml @@ -2,6 +2,8 @@ apiVersion: v1 kind: Service metadata: name: test + labels: + kubernetes.azure.com/generator: draft spec: type: LoadBalancer selector: diff --git a/test/templates/kustomize/overlays/production/deployment.yaml b/test/templates/kustomize/overlays/production/deployment.yaml index 434314b3..11621fba 100644 --- a/test/templates/kustomize/overlays/production/deployment.yaml +++ b/test/templates/kustomize/overlays/production/deployment.yaml @@ -2,6 +2,8 @@ apiVersion: apps/v1 kind: Deployment metadata: name: test + labels: + kubernetes.azure.com/generator: draft spec: template: spec: diff --git a/test/templates/kustomize/overlays/production/service.yaml b/test/templates/kustomize/overlays/production/service.yaml index 22be8bfd..177d24ee 100644 --- a/test/templates/kustomize/overlays/production/service.yaml +++ b/test/templates/kustomize/overlays/production/service.yaml @@ -2,5 +2,7 @@ apiVersion: v1 kind: Service metadata: name: test + labels: + kubernetes.azure.com/generator: draft spec: type: LoadBalancer diff --git a/test/templates/manifests/manifests/deployment.yaml b/test/templates/manifests/manifests/deployment.yaml index ad3b2549..d339c904 100644 --- a/test/templates/manifests/manifests/deployment.yaml +++ b/test/templates/manifests/manifests/deployment.yaml @@ -4,6 +4,7 @@ metadata: name: test labels: app: test + kubernetes.azure.com/generator: draft spec: replicas: 1 selector: diff --git a/test/templates/manifests/manifests/service.yaml b/test/templates/manifests/manifests/service.yaml index f6610fff..1cd13199 100644 --- a/test/templates/manifests/manifests/service.yaml +++ b/test/templates/manifests/manifests/service.yaml @@ -2,6 +2,8 @@ apiVersion: v1 kind: Service metadata: name: test + labels: + kubernetes.azure.com/generator: draft spec: type: LoadBalancer selector: diff --git a/test/templates/service_w_annotations.yaml b/test/templates/service_w_annotations.yaml index 841378cc..6d9f8224 100644 --- a/test/templates/service_w_annotations.yaml +++ b/test/templates/service_w_annotations.yaml @@ -2,5 +2,7 @@ apiVersion: v1 kind: Service metadata: name: my-app + labels: + kubernetes.azure.com/generator: draft spec: type: LoadBalancer diff --git a/test/templates/unsupported_no_of_containers.yaml b/test/templates/unsupported_no_of_containers.yaml index 908f51fb..9d91e05f 100644 --- a/test/templates/unsupported_no_of_containers.yaml +++ b/test/templates/unsupported_no_of_containers.yaml @@ -4,6 +4,7 @@ metadata: name: my-app labels: app: my-app + kubernetes.azure.com/generator: draft spec: replicas: 2 selector: From 8db86bd7d4b8ed51b872d01519e933f74461da81 Mon Sep 17 00:00:00 2001 From: Vidya Reddy <59590642+Vidya2606@users.noreply.github.com> Date: Wed, 15 May 2024 14:13:07 -0700 Subject: [PATCH 3/3] Added SDK calls for assignSpRole function (#272) --- cmd/setup-gh.go | 8 +++++ go.mod | 1 + go.sum | 2 ++ pkg/providers/az-client.go | 14 ++++++-- pkg/providers/azure.go | 23 ++++++++---- pkg/providers/azure_test.go | 64 +++++++++++++++++++++++++++++++++ pkg/providers/mock/az-client.go | 39 ++++++++++++++++++++ 7 files changed, 142 insertions(+), 9 deletions(-) diff --git a/cmd/setup-gh.go b/cmd/setup-gh.go index e75ad9f5..621fadaa 100644 --- a/cmd/setup-gh.go +++ b/cmd/setup-gh.go @@ -7,6 +7,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription" "github.com/Azure/draft/pkg/cred" "github.com/manifoldco/promptui" @@ -50,6 +51,13 @@ application and service principle, and will configure that application to trust sc.AzClient.GraphClient = graphClient + roleAssignmentClient, err := armauthorization.NewRoleAssignmentsClient(sc.SubscriptionID, azCred, nil) + if err != nil { + return fmt.Errorf("getting role assignment client: %w", err) + } + + sc.AzClient.RoleAssignClient = roleAssignmentClient + fillSetUpConfig(sc) s := spinner.CreateSpinner("--> Setting up Github OIDC...") diff --git a/go.mod b/go.mod index eb0d7b60..6ff1a307 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.0 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.2.0 github.com/briandowns/spinner v1.23.0 github.com/cenkalti/backoff/v4 v4.3.0 diff --git a/go.sum b/go.sum index 96b9e145..d853ad21 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 h1:FDif4R1+UUR+00q6wquyX github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2/go.mod h1:aiYBYui4BJ/BJCAIKs92XiPyQfTaBWqvHujDwKb6CBU= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization v1.0.0 h1:qtRcg5Y7jNJ4jEzPq4GpWLfTspHdNe2ZK6LjwGcjgmU= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization v1.0.0/go.mod h1:lPneRe3TwsoDRKY4O6YDLXHhEWrD+TIRa8XrV/3/fqw= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.2.0 h1:UrGzkHueDwAWDdjQxC+QaXHd4tVCkISYE9j7fSSXF8k= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.2.0/go.mod h1:qskvSQeW+cxEE2bcKYyKimB1/KiQ9xpJ99bcHY0BX6c= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= diff --git a/pkg/providers/az-client.go b/pkg/providers/az-client.go index 0c483435..7971304c 100644 --- a/pkg/providers/az-client.go +++ b/pkg/providers/az-client.go @@ -4,14 +4,18 @@ import ( "context" "errors" "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription" + msgraph "github.com/microsoftgraph/msgraph-sdk-go" ) type AzClient struct { - AzTenantClient azTenantClient - GraphClient GraphClient + AzTenantClient azTenantClient + GraphClient GraphClient + RoleAssignClient RoleAssignClient } //go:generate mockgen -source=./az-client.go -destination=./mock/az-client.go . @@ -43,3 +47,9 @@ func (g *GraphServiceClient) GetApplicationObjectId(ctx context.Context, appId s } return *appObjectId, nil } + +type RoleAssignClient interface { + CreateByID(ctx context.Context, roleAssignmentID string, parameters armauthorization.RoleAssignmentCreateParameters, options *armauthorization.RoleAssignmentsClientCreateByIDOptions) (armauthorization.RoleAssignmentsClientCreateByIDResponse, error) +} + +var _ RoleAssignClient = &armauthorization.RoleAssignmentsClient{} diff --git a/pkg/providers/azure.go b/pkg/providers/azure.go index d1f3affd..d0920ea2 100644 --- a/pkg/providers/azure.go +++ b/pkg/providers/azure.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription" "os/exec" "time" @@ -61,7 +62,7 @@ func InitiateAzureOIDCFlow(ctx context.Context, sc *SetUpCmd, s spinner.Spinner) return err } - if err := sc.assignSpRole(); err != nil { + if err := sc.assignSpRole(ctx); err != nil { return err } @@ -165,14 +166,22 @@ func (sc *SetUpCmd) CreateServicePrincipal() error { return nil } -func (sc *SetUpCmd) assignSpRole() error { +func (sc *SetUpCmd) assignSpRole(ctx context.Context) error { log.Debug("Assigning contributor role to service principal...") - scope := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", sc.SubscriptionID, sc.ResourceGroupName) - assignSpRoleCmd := exec.Command("az", "role", "assignment", "create", "--role", "contributor", "--subscription", sc.SubscriptionID, "--assignee-object-id", sc.spObjectId, "--assignee-principal-type", "ServicePrincipal", "--scope", scope, "--only-show-errors") - out, err := assignSpRoleCmd.CombinedOutput() + + objectID := sc.spObjectId + roleID := "contributor" + + parameters := armauthorization.RoleAssignmentCreateParameters{ + Properties: &armauthorization.RoleAssignmentProperties{ + PrincipalID: &objectID, + RoleDefinitionID: &roleID, + }, + } + + _, err := sc.AzClient.RoleAssignClient.CreateByID(ctx, roleID, parameters, nil) if err != nil { - log.Printf("%s\n", out) - return err + return fmt.Errorf("creating role assignment: %w", err) } log.Debug("Role assigned successfully!") diff --git a/pkg/providers/azure_test.go b/pkg/providers/azure_test.go index 6cc3c284..0dab8658 100644 --- a/pkg/providers/azure_test.go +++ b/pkg/providers/azure_test.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription" mock_providers "github.com/Azure/draft/pkg/providers/mock" "go.uber.org/mock/gomock" @@ -243,3 +244,66 @@ func TestGetAppObjectId_EmptyAppIdFromGraphClient(t *testing.T) { t.Errorf("Expected error '%v', got '%v'", expectedError, err) } } + +var principalId = "mockPrincipalID" +var roleDefId = "mockRoleDefinitionID" +var Id = "mockID" +var name = "mockName" +var Idtype = "mocktype" + +func TestAssignSpRole(t *testing.T) { + tests := []struct { + name string + expectedError error + mockResponse armauthorization.RoleAssignmentsClientCreateByIDResponse + }{ + { + name: "Success", + expectedError: nil, + mockResponse: armauthorization.RoleAssignmentsClientCreateByIDResponse{ + RoleAssignment: armauthorization.RoleAssignment{ + Properties: &armauthorization.RoleAssignmentPropertiesWithScope{ + PrincipalID: &principalId, + RoleDefinitionID: &roleDefId, + }, + ID: &Id, + Name: &name, + Type: &Idtype, + }, + }, + }, + { + name: "Error", + expectedError: errors.New("error"), + mockResponse: armauthorization.RoleAssignmentsClientCreateByIDResponse{}, + }, + { + name: "ErrorDuringRoleAssignment", + expectedError: errors.New("error during role assignment"), + mockResponse: armauthorization.RoleAssignmentsClientCreateByIDResponse{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockRoleAssignClient := mock_providers.NewMockRoleAssignClient(ctrl) + + mockRoleAssignClient.EXPECT().CreateByID(gomock.Any(), "contributor", gomock.Any(), gomock.Any()).Return(tt.mockResponse, tt.expectedError) + + sc := &SetUpCmd{ + AzClient: AzClient{ + RoleAssignClient: mockRoleAssignClient, + }, + spObjectId: "testObjectId", + } + + err := sc.assignSpRole(context.Background()) + if !errors.Is(err, tt.expectedError) { + t.Errorf("Expected error: %v, got: %v", tt.expectedError, err) + } + }) + } +} diff --git a/pkg/providers/mock/az-client.go b/pkg/providers/mock/az-client.go index bcf69751..7514ffc9 100644 --- a/pkg/providers/mock/az-client.go +++ b/pkg/providers/mock/az-client.go @@ -14,6 +14,7 @@ import ( reflect "reflect" runtime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + armauthorization "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization" armsubscription "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription" gomock "go.uber.org/mock/gomock" ) @@ -92,3 +93,41 @@ func (mr *MockGraphClientMockRecorder) GetApplicationObjectId(ctx, appId any) *g mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetApplicationObjectId", reflect.TypeOf((*MockGraphClient)(nil).GetApplicationObjectId), ctx, appId) } + +// MockRoleAssignClient is a mock of RoleAssignClient interface. +type MockRoleAssignClient struct { + ctrl *gomock.Controller + recorder *MockRoleAssignClientMockRecorder +} + +// MockRoleAssignClientMockRecorder is the mock recorder for MockRoleAssignClient. +type MockRoleAssignClientMockRecorder struct { + mock *MockRoleAssignClient +} + +// NewMockRoleAssignClient creates a new mock instance. +func NewMockRoleAssignClient(ctrl *gomock.Controller) *MockRoleAssignClient { + mock := &MockRoleAssignClient{ctrl: ctrl} + mock.recorder = &MockRoleAssignClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRoleAssignClient) EXPECT() *MockRoleAssignClientMockRecorder { + return m.recorder +} + +// CreateByID mocks base method. +func (m *MockRoleAssignClient) CreateByID(ctx context.Context, roleAssignmentID string, parameters armauthorization.RoleAssignmentCreateParameters, options *armauthorization.RoleAssignmentsClientCreateByIDOptions) (armauthorization.RoleAssignmentsClientCreateByIDResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateByID", ctx, roleAssignmentID, parameters, options) + ret0, _ := ret[0].(armauthorization.RoleAssignmentsClientCreateByIDResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateByID indicates an expected call of CreateByID. +func (mr *MockRoleAssignClientMockRecorder) CreateByID(ctx, roleAssignmentID, parameters, options any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateByID", reflect.TypeOf((*MockRoleAssignClient)(nil).CreateByID), ctx, roleAssignmentID, parameters, options) +}