diff --git a/go.mod b/go.mod index 6aaec60..3e53bac 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/onsi/gomega v1.27.10 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -22,6 +23,7 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 + github.com/tcnksm/go-gitconfig v0.1.2 golang.org/x/oauth2 v0.11.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 422a8c1..8ab36b6 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -35,6 +37,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw= +github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= @@ -44,6 +48,7 @@ golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBch golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= diff --git a/main.go b/main.go index 0f1e1a4..af168be 100644 --- a/main.go +++ b/main.go @@ -5,5 +5,9 @@ import ( ) func main() { + cmd.Init() } + +// TODO +// - [ ] Add tests diff --git a/pkg/atlantis/atlantis.go b/pkg/atlantis/atlantis.go index 9e8fd0a..5b1986e 100644 --- a/pkg/atlantis/atlantis.go +++ b/pkg/atlantis/atlantis.go @@ -88,7 +88,8 @@ func GenerateAtlantisYAML(prChangedFiles []string) error { } // Generate atlantis.yaml file err = generateOutputYAML(&atlantisConfig, - config.GlobalConfig.Parameters["output-file"]) + config.GlobalConfig.Parameters["output-file"], + config.GlobalConfig.Parameters["output-type"]) if err != nil { return err } @@ -201,11 +202,22 @@ func generateAtlantisConfig(autoMerge, parallelApply, parallelPlan, whenModified return config, err } -func generateOutputYAML(config *Config, outputFile string) error { +func generateOutputYAML(config *Config, outputFile string, outputType string) error { // Generate the atlantis.yaml file - yamlBytes, _ := yaml.Marshal(&config) - err := helpers.WriteFile(string(yamlBytes), outputFile) - return err + yamlBytes, err := yaml.Marshal(&config) + if err != nil { + return err + } + switch outputType { + case "file": + err = helpers.WriteFile(string(yamlBytes), outputFile) + return err + case "stdout": + fmt.Printf(string(yamlBytes)) + return nil + default: + return fmt.Errorf("output type '%s' is not supported", outputType) + } } func workflowFilter(info os.FileInfo, path, workflow, patternDetector string) bool { diff --git a/pkg/atlantis/atlantis_test.go b/pkg/atlantis/atlantis_test.go index b0042d7..0dd0825 100644 --- a/pkg/atlantis/atlantis_test.go +++ b/pkg/atlantis/atlantis_test.go @@ -565,9 +565,10 @@ func TestGenerateOutputYAML(t *testing.T) { // Define a sample output file path outputFile := "/tmp/test_output.yaml" + outputType := "file" // Call the function and generate the YAML - err := generateOutputYAML(config, outputFile) + err := generateOutputYAML(config, outputFile, outputType) if err != nil { t.Errorf("Error generating output YAML: %v", err) } @@ -603,6 +604,7 @@ func TestGenerateAtlantisYAML(t *testing.T) { config.GlobalConfig.Parameters["pattern-detector"] = "main.tf" config.GlobalConfig.Parameters["terraform-base-dir"] = "mockproject" config.GlobalConfig.Parameters["output-file"] = tempFile + config.GlobalConfig.Parameters["output-type"] = "file" config.GlobalConfig.Parameters["parallel-apply"] = "true" config.GlobalConfig.Parameters["parallel-plan"] = "true" config.GlobalConfig.Parameters["automerge"] = "true" @@ -611,4 +613,15 @@ func TestGenerateAtlantisYAML(t *testing.T) { assert.NoError(t, err) os.Remove(tempFile) + + config.GlobalConfig.Parameters["output-type"] = "undefined" + + err = GenerateAtlantisYAML(prChangedFiles) + assert.Error(t, err) + + config.GlobalConfig.Parameters["output-type"] = "stdout" + + err = GenerateAtlantisYAML(prChangedFiles) + assert.NoError(t, err) + } diff --git a/pkg/config/config.go b/pkg/config/config.go index 4a3a825..312b8a3 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -55,11 +55,18 @@ var ParameterList = []Parameter{ }, { Name: "output-file", - Description: "Atlantis output file name.", + Description: "Atlantis YAML output file name.", Required: false, DefaultValue: "atlantis.yaml", Shorthand: "f", }, + { + Name: "output-type", + Description: "Atlantis YAML output type. [file|stdout].", + Required: false, + DefaultValue: "file", + Shorthand: "e", + }, { Name: "workflow", Description: "Atlantis Workflow to be used. [single-workspace|multi-workspace].", @@ -69,7 +76,7 @@ var ParameterList = []Parameter{ }, { Name: "when-modified", - Description: "Atlantis When modified (list of strings) to run autoplan.", + Description: "Atlantis will trigger an autoplan when these modifications occur (list of strings).", Required: false, DefaultValue: "**/*.tf,**/*.tfvars,**/*.json,**/*.tpl,**/*.tmpl,**/*.xml", Shorthand: "m", @@ -118,8 +125,8 @@ var ParameterList = []Parameter{ }, { Name: "gh-token", - Description: "Github Token Value.", - Required: true, + Description: "Specify the GitHub token when automatic detection is not possible.", + Required: false, DefaultValue: "", Shorthand: "t", }, diff --git a/pkg/github/github.go b/pkg/github/github.go index 1aa6df5..643790c 100644 --- a/pkg/github/github.go +++ b/pkg/github/github.go @@ -2,9 +2,13 @@ package github import ( "context" + "errors" + "fmt" "strconv" + "strings" "github.com/google/go-github/github" + "github.com/tcnksm/go-gitconfig" "github.com/totmicro/atlantis-yaml-generator/pkg/config" "golang.org/x/oauth2" ) @@ -46,8 +50,18 @@ func runGHRequest(authToken, owner, repo, pullReqNum string) ([]string, error) { // GetChangedFiles gets the parameters to call a ghrequest that returns a list of changed files. func GetChangedFiles() (ChangedFiles []string, err error) { + // Parse the token from the git config file + token, _ := getTokenFromGitConfig() + if token == "" { + token = config.GlobalConfig.Parameters["gh-token"] + } + if token == "" { + err = errors.New("gh-token could not be parsed from .git/config file.\n" + + "Please use gh-token parameter or GH_TOKEN environment variable to set the token.") + return ChangedFiles, err + } prChangedFiles, err := runGHRequest( - config.GlobalConfig.Parameters["gh-token"], + token, config.GlobalConfig.Parameters["base-repo-owner"], config.GlobalConfig.Parameters["base-repo-name"], config.GlobalConfig.Parameters["pull-num"]) @@ -56,3 +70,29 @@ func GetChangedFiles() (ChangedFiles []string, err error) { } return prChangedFiles, err } + +func getTokenFromGitConfig() (string, error) { + url, err := gitconfig.Local("remote.origin.url") + if err != nil { + return "", fmt.Errorf("token not found in the [remote \"origin\"] block") + } + token, err := extractTokenFromURL(url) + return token, err +} + +func extractTokenFromURL(urlLine string) (string, error) { + // Split by "x-access-token:" to extract the token. + parts := strings.Split(urlLine, "x-access-token:") + if len(parts) == 2 { + tokenPart := parts[1] + + // Split again by "@" to extract the token. + tokenParts := strings.Split(tokenPart, "@") + if len(tokenParts) >= 1 { + return tokenParts[0], nil + } + } + + return "", fmt.Errorf("token not found in url line") + +}