diff --git a/pkg/embedutils/embedutil.go b/pkg/embedutils/embedutil.go index c3a91be9..5a11acb1 100644 --- a/pkg/embedutils/embedutil.go +++ b/pkg/embedutils/embedutil.go @@ -22,3 +22,29 @@ func EmbedFStoMap(embedFS embed.FS, path string) (map[string]fs.DirEntry, error) return mapping, nil } + +func EmbedFStoMapWithFiles(embedFS fs.FS, path string) (map[string]fs.DirEntry, error) { + files, err := fs.ReadDir(embedFS, path) + if err != nil { + return nil, fmt.Errorf("failed to readDir: %w", err) + } + + mapping := make(map[string]fs.DirEntry) + + for _, f := range files { + mapping[f.Name()] = f + if f.IsDir() { + add, err := EmbedFStoMapWithFiles(embedFS, path+"/"+f.Name()) + if err != nil { + return nil, err + } + for k, v := range add { + mapping[f.Name()+"/"+k] = v + } + } else { + mapping[f.Name()] = f + } + } + + return mapping, nil +} diff --git a/pkg/workflows/workflows.go b/pkg/workflows/workflows.go index 8b6b79b3..a85f1c2a 100644 --- a/pkg/workflows/workflows.go +++ b/pkg/workflows/workflows.go @@ -180,7 +180,7 @@ func createWorkflowsFromEmbedFS(workflowTemplates embed.FS, dest string) *Workfl w := &Workflows{ workflows: deployMap, - dest: path.Join(dest, ".github", "workflows"), + dest: dest, configs: make(map[string]*config.DraftConfig), workflowTemplates: workflowTemplates, } diff --git a/pkg/workflows/workflows_test.go b/pkg/workflows/workflows_test.go index 6f20eb52..6f720f75 100644 --- a/pkg/workflows/workflows_test.go +++ b/pkg/workflows/workflows_test.go @@ -1,18 +1,150 @@ package workflows import ( + "fmt" "io" + "io/fs" "io/ioutil" "os" "testing" + "testing/fstest" "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" "k8s.io/client-go/kubernetes/scheme" + "github.com/Azure/draft/pkg/config" + "github.com/Azure/draft/pkg/embedutils" "github.com/Azure/draft/pkg/templatewriter/writers" + "github.com/Azure/draft/template" ) +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"} + err := createTempDeploymentFile("charts", "charts/production.yaml", "../../test/templates/helm/charts/production.yaml") + assert.Nil(t, err) + assert.Nil(t, CreateWorkflows(dest, deployType, flagVariables, templatewriter, flagValuesMap)) + os.RemoveAll("charts") + os.RemoveAll(".github") + + deployType = "kustomize" + err = createTempDeploymentFile("overlays/production", "overlays/production/deployment.yaml", "../../test/templates/kustomize/overlays/production/deployment.yaml") + assert.Nil(t, err) + assert.Nil(t, CreateWorkflows(dest, deployType, flagVariables, templatewriter, flagValuesMap)) + os.RemoveAll("overlays") + os.RemoveAll(".github") + + deployType = "manifests" + err = createTempDeploymentFile("manifests", "manifests/deployment.yaml", "../../test/templates/manifests/manifests/deployment.yaml") + assert.Nil(t, err) + assert.Nil(t, CreateWorkflows(dest, deployType, flagVariables, templatewriter, flagValuesMap)) + os.RemoveAll("manifests") + os.RemoveAll(".github") + +} +func TestUpdateProductionDeployments(t *testing.T) { + flagValuesMap := map[string]string{"AZURECONTAINERREGISTRY": "testRegistry", "CONTAINERNAME": "testContainer"} + testTemplateWriter := &writers.LocalFSWriter{} + assert.Nil(t, updateProductionDeployments("", ".", flagValuesMap, testTemplateWriter)) + + helmFileName, _ := createTempManifest("../../test/templates/helm_prod_values.yaml") + deploymentFileName, _ := createTempManifest("../../test/templates/deployment.yaml") + defer os.Remove(helmFileName) + defer os.Remove(deploymentFileName) + + assert.Nil(t, setHelmContainerImage(helmFileName, "testImage", testTemplateWriter)) + + helmDeploy := &HelmProductionYaml{} + assert.Nil(t, helmDeploy.LoadFromFile(helmFileName)) + assert.Equal(t, "testImage", helmDeploy.Image.Repository) + + assert.Nil(t, setDeploymentContainerImage(deploymentFileName, "testImage")) + decode := scheme.Codecs.UniversalDeserializer().Decode + file, err := ioutil.ReadFile(deploymentFileName) + assert.Nil(t, err) + + k8sObj, _, err := decode(file, nil, nil) + assert.Nil(t, err) + + deploy, ok := k8sObj.(*appsv1.Deployment) + assert.True(t, ok) + assert.Equal(t, "testImage", deploy.Spec.Template.Spec.Containers[0].Image) +} + +func TestLoadConfig(t *testing.T) { + fakeFS, err := createMockWorkflowTemplatesFS() + assert.Nil(t, err) + w, err := createMockWorkflow("workflows", fakeFS) + assert.Nil(t, err) + + // existing deployType test + _, err = w.loadConfig("helm") + assert.Nil(t, err) + + _, err = w.loadConfig("kustomize") + assert.Nil(t, err) + + _, err = w.loadConfig("manifests") + assert.Nil(t, err) + + // deployType unsupported test + _, err = w.loadConfig("fake") + assert.NotNil(t, err) + + // file does not exist test + _, err = w.loadConfig("emptyDir") + assert.NotNil(t, err) + + // file does not exist test + _, err = w.loadConfig("corrupted") + assert.NotNil(t, err) +} + +func TestPopulateConfigs(t *testing.T) { + fakeFS, err := createMockWorkflowTemplatesFS() + assert.Nil(t, err) + + w, err := createMockWorkflow("workflows", fakeFS) + assert.Nil(t, err) + + w.populateConfigs() + assert.Equal(t, 5, len(w.configs)) // includes emptyDir and corrupted so 2 additional configs + + w, err = createTestWorkflowEmbed("workflows") + assert.Nil(t, err) + + w.populateConfigs() + assert.Equal(t, 3, len(w.configs)) +} + +func TestCreateWorkflowFiles(t *testing.T) { + templatewriter := &writers.LocalFSWriter{} + customInputs := map[string]string{"AZURECONTAINERREGISTRY": "testAcr", "CONTAINERNAME": "testContainer", "RESOURCEGROUP": "testRG", "CLUSTERNAME": "testCluster", "BRANCHNAME": "testBranch", "CHARTPATH": "testPath", "CHARTOVERRIDEPATH": "testOverridePath"} + badInputs := map[string]string{} + + workflowTemplate, err := createMockWorkflowTemplatesFS() + assert.Nil(t, err) + + mockWF, err := createMockWorkflow("workflows", workflowTemplate) + assert.Nil(t, err) + + mockWF.populateConfigs() + + err = mockWF.createWorkflowFiles("fakeDeployType", customInputs, templatewriter) + assert.NotNil(t, err) + + err = mockWF.createWorkflowFiles("helm", customInputs, templatewriter) + assert.Nil(t, err) + + err = mockWF.createWorkflowFiles("helm", badInputs, templatewriter) + assert.NotNil(t, err) + +} + func createTempManifest(path string) (string, error) { file, err := ioutil.TempFile("", "*.yaml") if err != nil { @@ -58,58 +190,85 @@ func createTempDeploymentFile(dirPath, fileName, path string) error { } return nil } -func TestCreateWorkflows(t *testing.T) { + +// Creates a copy of the embeded files in memory and returns a passable fs to use for testing +func createMockWorkflowTemplatesFS() (fs.FS, error) { + rootPath := "workflows/" + embedFiles, err := embedutils.EmbedFStoMapWithFiles(template.Workflows, "workflows") + if err != nil { + return nil, fmt.Errorf("failed to readDir: %w in embeded files", err) + } + + mockFS := fstest.MapFS{} + + for path, file := range embedFiles { + if file.IsDir() { + mockFS[rootPath+path] = &fstest.MapFile{Mode: fs.ModeDir} + } else { + bytes, err := template.Workflows.ReadFile(rootPath + path) + if err != nil { + return nil, fmt.Errorf("failed to read file: %w", err) + } + mockFS[rootPath+path] = &fstest.MapFile{Data: bytes} + } + } + + mockFS[rootPath+"emptyDir"] = &fstest.MapFile{Mode: fs.ModeDir} + mockFS[rootPath+"corrupted"] = &fstest.MapFile{Mode: fs.ModeDir} + mockFS[rootPath+"corrupted/draft.yaml"] = &fstest.MapFile{Data: []byte("fake yaml data")} + + return mockFS, nil +} + +func createMockWorkflow(dirPath string, mockWorkflowTemplates fs.FS) (*Workflows, error) { dest := "." - deployType := "helm" - flagVariables := []string{} - templatewriter := &writers.LocalFSWriter{} - flagValuesMap := map[string]string{"AZURECONTAINERREGISTRY": "testAcr", "CONTAINERNAME": "testContainer", "RESOURCEGROUP": "testRG", "CLUSTERNAME": "testCluster", "BRANCHNAME": "testBranch"} - err := createTempDeploymentFile("charts", "charts/production.yaml", "../../test/templates/helm/charts/production.yaml") - assert.Nil(t, err) - assert.Nil(t, CreateWorkflows(dest, deployType, flagVariables, templatewriter, flagValuesMap)) - os.RemoveAll("charts") - os.RemoveAll(".github") - deployType = "kustomize" - err = createTempDeploymentFile("overlays/production", "overlays/production/deployment.yaml", "../../test/templates/kustomize/overlays/production/deployment.yaml") - assert.Nil(t, err) - assert.Nil(t, CreateWorkflows(dest, deployType, flagVariables, templatewriter, flagValuesMap)) - os.RemoveAll("overlays") - os.RemoveAll(".github") + deployMap, err := fsToMap(mockWorkflowTemplates, dirPath) + if err != nil { + return nil, fmt.Errorf("failed fsToMap: %w", err) + } - deployType = "manifests" - err = createTempDeploymentFile("manifests", "manifests/deployment.yaml", "../../test/templates/manifests/manifests/deployment.yaml") - assert.Nil(t, err) - assert.Nil(t, CreateWorkflows(dest, deployType, flagVariables, templatewriter, flagValuesMap)) - os.RemoveAll("manifests") - os.RemoveAll(".github") + w := &Workflows{ + workflows: deployMap, + dest: dest, + configs: make(map[string]*config.DraftConfig), + workflowTemplates: mockWorkflowTemplates, + } + return w, nil } -func TestUpdateProductionDeployments(t *testing.T) { - flagValuesMap := map[string]string{"AZURECONTAINERREGISTRY": "testRegistry", "CONTAINERNAME": "testContainer"} - testTemplateWriter := &writers.LocalFSWriter{} - assert.Nil(t, updateProductionDeployments("", ".", flagValuesMap, testTemplateWriter)) - helmFileName, _ := createTempManifest("../../test/templates/helm_prod_values.yaml") - deploymentFileName, _ := createTempManifest("../../test/templates/deployment.yaml") - defer os.Remove(helmFileName) - defer os.Remove(deploymentFileName) +func createTestWorkflowEmbed(dirPath string) (*Workflows, error) { + dest := "." - assert.Nil(t, setHelmContainerImage(helmFileName, "testImage", testTemplateWriter)) + deployMap, err := embedutils.EmbedFStoMap(template.Workflows, "workflows") + if err != nil { + return nil, fmt.Errorf("failed to create deployMap: %w", err) + } - helmDeploy := &HelmProductionYaml{} - assert.Nil(t, helmDeploy.LoadFromFile(helmFileName)) - assert.Equal(t, "testImage", helmDeploy.Image.Repository) + w := &Workflows{ + workflows: deployMap, + dest: dest, + configs: make(map[string]*config.DraftConfig), + workflowTemplates: template.Workflows, + } - assert.Nil(t, setDeploymentContainerImage(deploymentFileName, "testImage")) - decode := scheme.Codecs.UniversalDeserializer().Decode - file, err := ioutil.ReadFile(deploymentFileName) - assert.Nil(t, err) + return w, nil +} - k8sObj, _, err := decode(file, nil, nil) - assert.Nil(t, err) +func fsToMap(fsFS fs.FS, path string) (map[string]fs.DirEntry, error) { + files, err := fs.ReadDir(fsFS, path) + if err != nil { + return nil, fmt.Errorf("failed to ReadDir: %w", err) + } - deploy, ok := k8sObj.(*appsv1.Deployment) - assert.True(t, ok) - assert.Equal(t, "testImage", deploy.Spec.Template.Spec.Containers[0].Image) + mapping := make(map[string]fs.DirEntry) + + for _, f := range files { + if f.IsDir() { + mapping[f.Name()] = f + } + } + + return mapping, nil } diff --git a/test/gen_integration.sh b/test/gen_integration.sh index 8200c9da..ef8b0990 100755 --- a/test/gen_integration.sh +++ b/test/gen_integration.sh @@ -1,9 +1,11 @@ +export WORKFLOWS_PATH=.github/workflows + # remove previous tests echo "Removing previous integration configs" rm -rf ./integration/* echo "Removing previous integration workflows" -rm ../.github/workflows/integration-linux.yml -rm ../.github/workflows/integration-windows.yml +rm ../$WORKFLOWS_PATH/integration-linux.yml +rm ../$WORKFLOWS_PATH/integration-windows.yml # create temp files for keeping track off workflow jobs to build job-dependency graph # this is used to populated the needs: field of the required final workflow jobs @@ -48,7 +50,7 @@ jobs: with: name: draft-binary path: ./draft - if-no-files-found: error" > ../.github/workflows/integration-linux.yml + if-no-files-found: error" > ../$WORKFLOWS_PATH/integration-linux.yml echo "name: draft Windows Integrations @@ -92,7 +94,7 @@ jobs: with: name: check_windows_addon_kustomize path: ./test/check_windows_addon_kustomize.ps1 - if-no-files-found: error" > ../.github/workflows/integration-windows.yml + if-no-files-found: error" > ../$WORKFLOWS_PATH/integration-windows.yml # read config and add integration test for each language @@ -309,7 +311,7 @@ languageVariables: action-validator 0.1.2 - name: Lint Actions run: | - find .github/workflows -type f \( -iname \*.yaml -o -iname \*.yml \) \ + find $WORKFLOWS_PATH -type f \( -iname \*.yaml -o -iname \*.yml \) \ | xargs -I {} action-validator --verbose {} - name: Execute dry run for update command run: | @@ -326,7 +328,7 @@ languageVariables: run: kubectl get po - name: Fail if any error if: steps.deploy.outcome != 'success' - run: exit 6" >> ../.github/workflows/integration-linux.yml + run: exit 6" >> ../$WORKFLOWS_PATH/integration-linux.yml # create kustomize workflow kustomize_create_update_job_name=$lang-kustomize-create-update @@ -451,7 +453,7 @@ languageVariables: action-validator 0.1.2 - name: Lint Actions run: | - find .github/workflows -type f \( -iname \*.yaml -o -iname \*.yml \) \ + find $WORKFLOWS_PATH -type f \( -iname \*.yaml -o -iname \*.yml \) \ | xargs -I {} action-validator --verbose {} - name: Execute dry run for update command run: | @@ -467,7 +469,7 @@ languageVariables: run: kubectl get po - name: Fail if any error if: steps.deploy.outcome != 'success' - run: exit 6" >> ../.github/workflows/integration-linux.yml + run: exit 6" >> ../$WORKFLOWS_PATH/integration-linux.yml # create manifests workflow manifest_update_job_name=$lang-manifest-update @@ -585,7 +587,7 @@ languageVariables: action-validator 0.1.2 - name: Lint Actions run: | - find .github/workflows -type f \( -iname \*.yaml -o -iname \*.yml \) \ + find $WORKFLOWS_PATH -type f \( -iname \*.yaml -o -iname \*.yml \) \ | xargs -I {} action-validator --verbose {} - uses: actions/upload-artifact@v3 with: @@ -641,7 +643,7 @@ languageVariables: run: kubectl get po - name: Fail if any error if: steps.deploy.outcome != 'success' - run: exit 6" >> ../.github/workflows/integration-linux.yml + run: exit 6" >> ../$WORKFLOWS_PATH/integration-linux.yml helm_update_win_jobname=$lang-helm-update echo $helm_update_win_jobname >> $helm_win_workflow_names_file @@ -693,7 +695,7 @@ languageVariables: name: check_windows_addon_helm path: ./langtest/ - run: ./check_windows_addon_helm.ps1 - working-directory: ./langtest/" >> ../.github/workflows/integration-windows.yml + working-directory: ./langtest/" >> ../$WORKFLOWS_PATH/integration-windows.yml # create kustomize workflow kustomize_win_workflow_name=$lang-kustomize-update @@ -745,7 +747,7 @@ languageVariables: path: ./langtest/ - run: ./check_windows_addon_kustomize.ps1 working-directory: ./langtest/ - " >> ../.github/workflows/integration-windows.yml + " >> ../$WORKFLOWS_PATH/integration-windows.yml done echo " @@ -754,7 +756,7 @@ echo " needs: [ $( paste -sd ',' $helm_win_workflow_names_file) ] steps: - run: echo "helm integrations passed" -" >> ../.github/workflows/integration-windows.yml +" >> ../$WORKFLOWS_PATH/integration-windows.yml echo " kustomize-win-integrations-summary: @@ -762,7 +764,7 @@ echo " needs: [ $( paste -sd ',' $kustomize_win_workflow_names_file) ] steps: - run: echo "kustomize integrations passed" -" >> ../.github/workflows/integration-windows.yml +" >> ../$WORKFLOWS_PATH/integration-windows.yml echo " helm-integrations-summary: @@ -770,7 +772,7 @@ echo " needs: [ $( paste -sd ',' $helm_workflow_names_file) ] steps: - run: echo "helm integrations passed" -" >> ../.github/workflows/integration-linux.yml +" >> ../$WORKFLOWS_PATH/integration-linux.yml echo " kustomize-integrations-summary: @@ -778,7 +780,7 @@ echo " needs: [ $( paste -sd ',' $kustomize_workflow_names_file) ] steps: - run: echo "kustomize integrations passed" -" >> ../.github/workflows/integration-linux.yml +" >> ../$WORKFLOWS_PATH/integration-linux.yml echo " manifest-integrations-summary: @@ -786,4 +788,4 @@ echo " needs: [ $( paste -sd ',' $manifest_workflow_names_file) ] steps: - run: echo "manifest integrations passed" -" >> ../.github/workflows/integration-linux.yml \ No newline at end of file +" >> ../$WORKFLOWS_PATH/integration-linux.yml