diff --git a/pkg/azurePipelines/azurePipelines.go b/pkg/azurePipelines/azurePipelines.go new file mode 100644 index 00000000..f4defc19 --- /dev/null +++ b/pkg/azurePipelines/azurePipelines.go @@ -0,0 +1,133 @@ +package azurePipelines + +import ( + "embed" + "fmt" + "io/fs" + "path" + + "github.com/Azure/draft/pkg/config" + "github.com/Azure/draft/pkg/embedutils" + "github.com/Azure/draft/pkg/osutil" + "github.com/Azure/draft/pkg/templatewriter" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" +) + +const ( + pipelineParentDirName = "azurePipelines" + aksPipelineTemplateFileName = "azure-kubernetes-service.yaml" + configFileName = "draft.yaml" + pipelineNameVar = "PIPELINENAME" +) + +type AzurePipelines struct { + pipelines map[string]fs.DirEntry + configs map[string]*config.DraftConfig + dest string + pipelineTemplates embed.FS +} + +func CreatePipelinesFromEmbedFS(pipelineTemplates embed.FS, dest string) (*AzurePipelines, error) { + pipelineMap, err := embedutils.EmbedFStoMap(pipelineTemplates, "azurePipelines") + if err != nil { + return nil, fmt.Errorf("error creating map from embedded FS: %w", err) + } + + p := &AzurePipelines{ + pipelines: pipelineMap, + dest: dest, + configs: make(map[string]*config.DraftConfig), + pipelineTemplates: pipelineTemplates, + } + p.populateConfigs() + + return p, nil + +} + +func (p *AzurePipelines) populateConfigs() { + for _, val := range p.pipelines { + draftConfig, err := p.loadConfig(val.Name()) + if err != nil { + log.Debugf("error loading draftConfig for pipeline of deploy type %s: %v", val.Name(), err) + draftConfig = &config.DraftConfig{} + } + p.configs[val.Name()] = draftConfig + } +} + +func (p *AzurePipelines) GetConfig(deployType string) (*config.DraftConfig, error) { + val, ok := p.configs[deployType] + if !ok { + return nil, fmt.Errorf("deploy type %s unsupported", deployType) + } + return val, nil +} + +func (p *AzurePipelines) loadConfig(deployType string) (*config.DraftConfig, error) { + val, ok := p.pipelines[deployType] + if !ok { + return nil, fmt.Errorf("deploy type %s unsupported", deployType) + } + + configPath := path.Join(pipelineParentDirName, val.Name(), configFileName) + configBytes, err := fs.ReadFile(p.pipelineTemplates, configPath) + if err != nil { + return nil, fmt.Errorf("error reading config file: %w", err) + } + + var draftConfig config.DraftConfig + if err = yaml.Unmarshal(configBytes, &draftConfig); err != nil { + return nil, fmt.Errorf("error unmarshalling config file: %w", err) + } + + return &draftConfig, nil +} + +func (p *AzurePipelines) overrideFilename(draftConfig *config.DraftConfig, srcDir string) error { + if draftConfig.FileNameOverrideMap == nil { + draftConfig.FileNameOverrideMap = make(map[string]string) + } + pipelineVar, err := draftConfig.GetVariable(pipelineNameVar) + if err != nil { + return fmt.Errorf("error getting pipeline name variable: %w", err) + } + + if err = fs.WalkDir(p.pipelineTemplates, srcDir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.Name() == aksPipelineTemplateFileName { + draftConfig.FileNameOverrideMap[d.Name()] = pipelineVar.Value + ".yaml" + } + return nil + }); err != nil { + return fmt.Errorf("error walking through source directory: %w", err) + } + + return nil +} + +func (p *AzurePipelines) CreatePipelineFiles(deployType string, draftConfig *config.DraftConfig, templateWriter templatewriter.TemplateWriter) error { + val, ok := p.pipelines[deployType] + if !ok { + return fmt.Errorf("deploy type %s currently unsupported for azure pipeline", deployType) + } + srcDir := path.Join(pipelineParentDirName, val.Name()) + log.Debugf("source directory of pipeline template: %s", srcDir) + + if err := p.overrideFilename(draftConfig, srcDir); err != nil { + return fmt.Errorf("error overriding filename: %w", err) + } + + if err := draftConfig.ApplyDefaultVariables(); err != nil { + return fmt.Errorf("error applying default variables: %w", err) + } + + if err := osutil.CopyDir(p.pipelineTemplates, srcDir, p.dest, draftConfig, templateWriter); err != nil { + return fmt.Errorf("error copying pipeline files: %w", err) + } + + return nil +} diff --git a/pkg/azurePipelines/azurePipelines_test.go b/pkg/azurePipelines/azurePipelines_test.go new file mode 100644 index 00000000..7e781083 --- /dev/null +++ b/pkg/azurePipelines/azurePipelines_test.go @@ -0,0 +1,160 @@ +package azurePipelines + +import ( + "fmt" + "os" + "testing" + + "github.com/Azure/draft/pkg/config" + "github.com/Azure/draft/pkg/templatewriter/writers" + "github.com/Azure/draft/template" + "github.com/stretchr/testify/assert" +) + +func TestCreatePipelines(t *testing.T) { + var pipelineFilePath string + templateWriter := &writers.LocalFSWriter{} + + tests := []struct { + name string + deployType string + shouldError bool + setConfig func(dc *config.DraftConfig) + cleanUp func(tempDir string) + }{ + { + name: "kustomize_default_path", + deployType: "kustomize", + shouldError: false, + }, + { + name: "kustomize_given_path", + deployType: "kustomize", + shouldError: false, + setConfig: func(dc *config.DraftConfig) { + dc.SetVariable("KUSTOMIZEPATH", "test/kustomize/overlays/production") + }, + }, + { + name: "manifests_default_path", + deployType: "manifests", + shouldError: false, + setConfig: func(dc *config.DraftConfig) { + dc.SetVariable("PIPELINENAME", "some-other-name") + }, + }, + { + name: "manifests_custom_path", + deployType: "manifests", + shouldError: false, + setConfig: func(dc *config.DraftConfig) { + dc.SetVariable("MANIFESTPATH", "test/manifests") + }, + }, + { + name: "invalid", + deployType: "invalid", + shouldError: true, + }, + { + name: "missing_config", + deployType: "kustomize", + shouldError: true, + setConfig: func(dc *config.DraftConfig) { + // removing the last variable from draftConfig + dc.Variables = dc.Variables[:len(dc.Variables)-1] + }, + }, + } + + for _, tt := range tests { + draftConfig := newDraftConfig() + + tempDir, err := os.MkdirTemp(".", "testTempDir") + assert.Nil(t, err) + + if tt.setConfig != nil { + tt.setConfig(draftConfig) + } + + pipelines, err := CreatePipelinesFromEmbedFS(template.AzurePipelines, tempDir) + assert.Nil(t, err) + + err = pipelines.CreatePipelineFiles(tt.deployType, draftConfig, templateWriter) + + pipelineFilePath = fmt.Sprintf("%s/.pipelines/%s", tempDir, aksPipelineTemplateFileName) + if val, ok := draftConfig.FileNameOverrideMap[aksPipelineTemplateFileName]; ok { + pipelineFilePath = fmt.Sprintf("%s/.pipelines/%s", tempDir, val) + } + + if tt.shouldError { + assert.NotNil(t, err) + _, err = os.Stat(pipelineFilePath) + assert.Equal(t, os.IsNotExist(err), true) + } else { + assert.Nil(t, err) + _, err = os.Stat(pipelineFilePath) + assert.Nil(t, err) + } + + err = os.RemoveAll(tempDir) + assert.Nil(t, err) + } +} + +func newDraftConfig() *config.DraftConfig { + return &config.DraftConfig{ + Variables: []*config.BuilderVar{ + { + Name: "PIPELINENAME", + Value: "testPipeline", + }, + { + Name: "BRANCHNAME", + Default: config.BuilderVarDefault{ + Value: "main", + }, + }, + { + Name: "ARMSERVICECONNECTION", + Value: "testServiceConnection", + }, + { + Name: "AZURECONTAINERREGISTRY", + Value: "testACR", + }, + { + Name: "CONTAINERNAME", + Value: "testContainer", + }, + { + Name: "CLUSTERRESOURCEGROUP", + Value: "testRG", + }, + { + Name: "ACRRESOURCEGROUP", + Value: "testACRRG", + }, + { + Name: "CLUSTERNAME", + Value: "testCluster", + }, + { + Name: "KUSTOMIZEPATH", + Default: config.BuilderVarDefault{ + Value: "kustomize/overlays/production", + }, + }, + { + Name: "MANIFESTPATH", + Default: config.BuilderVarDefault{ + Value: "manifests", + }, + }, + { + Name: "NAMESPACE", + Value: "testNamespace", + }, + }, + } +} diff --git a/pkg/config/draftconfig.go b/pkg/config/draftconfig.go index 93aa0b4d..0e81abba 100644 --- a/pkg/config/draftconfig.go +++ b/pkg/config/draftconfig.go @@ -9,9 +9,10 @@ import ( // TODO: remove Name Overrides since we don't need them anymore type DraftConfig struct { - DisplayName string `yaml:"displayName"` - NameOverrides []FileNameOverride `yaml:"nameOverrides"` - Variables []*BuilderVar `yaml:"variables"` + DisplayName string `yaml:"displayName"` + NameOverrides []FileNameOverride `yaml:"nameOverrides"` + Variables []*BuilderVar `yaml:"variables"` + FileNameOverrideMap map[string]string `yaml:"filenameOverrideMap"` nameOverrideMap map[string]string } diff --git a/pkg/osutil/osutil.go b/pkg/osutil/osutil.go index 14e0ce8a..dcda139f 100644 --- a/pkg/osutil/osutil.go +++ b/pkg/osutil/osutil.go @@ -94,8 +94,12 @@ func CopyDir( continue } + fileName := f.Name() + if overrideName, ok := draftConfig.FileNameOverrideMap[f.Name()]; ok { + fileName = overrideName + } srcPath := path.Join(src, f.Name()) - destPath := path.Join(dest, f.Name()) + destPath := path.Join(dest, fileName) log.Debugf("Source path: %s Dest path: %s", srcPath, destPath) if f.IsDir() { diff --git a/template/azurePipelines/kustomize/.pipelines/azure-kubernetes-service-kustomize.yaml b/template/azurePipelines/kustomize/.pipelines/azure-kubernetes-service.yaml similarity index 100% rename from template/azurePipelines/kustomize/.pipelines/azure-kubernetes-service-kustomize.yaml rename to template/azurePipelines/kustomize/.pipelines/azure-kubernetes-service.yaml diff --git a/template/azurePipelines/manifests/draft.yml b/template/azurePipelines/manifests/draft.yaml similarity index 100% rename from template/azurePipelines/manifests/draft.yml rename to template/azurePipelines/manifests/draft.yaml