diff --git a/.github/workflows/release-go.yml b/.github/workflows/release-go.yml new file mode 100644 index 00000000..cf2cdd19 --- /dev/null +++ b/.github/workflows/release-go.yml @@ -0,0 +1,27 @@ +name: Release Go + +on: + push: + branches: [release/*] + +jobs: + read-version: + name: Read version to release + runs-on: ubuntu-latest + outputs: + version: ${{ steps.versions.outputs.changelog-latest-version }} + steps: + - uses: actions/checkout@v3 + - uses: cucumber/action-get-versions@v1.0.0 + id: versions + + publish-go: + name: Create go/v* tag + runs-on: ubuntu-latest + needs: read-version + steps: + - uses: actions/checkout@v3 + - name: Create git tag + run: | + git tag "go/v${{ needs.read-version.outputs.version }}" + git push --tags \ No newline at end of file diff --git a/.github/workflows/test-go.yml b/.github/workflows/test-go.yml new file mode 100644 index 00000000..9ba22b16 --- /dev/null +++ b/.github/workflows/test-go.yml @@ -0,0 +1,32 @@ +name: test-go +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_call: + +jobs: + test: + strategy: + matrix: + go-version: [ 1.17.x ] + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - name: lint + working-directory: go + run: gofmt -w . + - name: Run go vet + working-directory: go + run: go vet ./... + - name: Run go test + working-directory: go + run: make test diff --git a/CHANGELOG.md b/CHANGELOG.md index fd8e0510..5277e71b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- [Go] added ci-environment implementation in Go ## [9.0.4] - 2022-03-06 ### Fixed diff --git a/README.md b/README.md index f84380ce..c731bc11 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![test-java](https://github.com/cucumber/ci-environment/actions/workflows/test-java.yml/badge.svg)](https://github.com/cucumber/ci-environment/actions/workflows/test-java.yml) [![test-javascript](https://github.com/cucumber/ci-environment/actions/workflows/test-javascript.yml/badge.svg)](https://github.com/cucumber/ci-environment/actions/workflows/test-javascript.yml) [![test-ruby](https://github.com/cucumber/ci-environment/actions/workflows/test-ruby.yml/badge.svg)](https://github.com/cucumber/ci-environment/actions/workflows/test-ruby.yml) +[![test-go](https://github.com/cucumber/ci-environment/actions/workflows/test-go.yml/badge.svg)](https://github.com/cucumber/ci-environment/actions/workflows/test-go.yml) This library detects the CI environment based on environment variables defined by CI servers. @@ -76,6 +77,26 @@ ci_environment = Cucumber::CiEnvironment.detect_ci_environment(ENV) p ci_environment ``` +### Go + +```shell +go get github.com/cucumber/ci-environment/go@latest +``` + +```Go +import ( + "fmt" + cienvironment "github.com/cucumber/ci-environment/go" +) + +func main() { + ci := cienvironment.DetectCIEnvironment() + if ci == nil { + fmt.Println("No CI environment detected") + } +} +``` + ## Supported CI servers * [Azure Pipelines](https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?tabs=yaml&view=azure-devops#build-variables) diff --git a/go/CiEnvironments.json b/go/CiEnvironments.json new file mode 100644 index 00000000..e58e651f --- /dev/null +++ b/go/CiEnvironments.json @@ -0,0 +1,150 @@ +[ + { + "name": "Azure Pipelines", + "url": "${BUILD_BUILDURI}", + "buildNumber": "${BUILD_BUILDNUMBER}", + "git": { + "remote": "${BUILD_REPOSITORY_URI}", + "revision": "${BUILD_SOURCEVERSION}", + "branch": "${BUILD_SOURCEBRANCH/refs\/heads\/(.*)/\\1}", + "tag": "${BUILD_SOURCEBRANCH/refs\/tags\/(.*)/\\1}" + } + }, + { + "name": "Bamboo", + "url": "${bamboo_buildResultsUrl}", + "buildNumber": "${bamboo_buildNumber}", + "git": { + "remote": "${bamboo_planRepository_repositoryUrl}", + "revision": "${bamboo_planRepository_revision}", + "branch": "${bamboo_planRepository_branch}" + } + }, + { + "name": "Buddy", + "url": "${BUDDY_EXECUTION_URL}", + "buildNumber": "${BUDDY_EXECUTION_ID}", + "git": { + "remote": "${BUDDY_SCM_URL}", + "revision": "${BUDDY_EXECUTION_REVISION}", + "branch": "${BUDDY_EXECUTION_BRANCH}", + "tag": "${BUDDY_EXECUTION_TAG}" + } + }, + { + "name": "Bitrise", + "url": "${BITRISE_BUILD_URL}", + "buildNumber": "${BITRISE_BUILD_NUMBER}", + "git": { + "remote": "${GIT_REPOSITORY_URL}", + "revision": "${BITRISE_GIT_COMMIT}", + "branch": "${BITRISE_GIT_BRANCH}", + "tag": "${BITRISE_GIT_TAG}" + } + }, + { + "name": "CircleCI", + "url": "${CIRCLE_BUILD_URL}", + "buildNumber": "${CIRCLE_BUILD_NUM}", + "git": { + "remote": "${CIRCLE_REPOSITORY_URL}", + "revision": "${CIRCLE_SHA1}", + "branch": "${CIRCLE_BRANCH}", + "tag": "${CIRCLE_TAG}" + } + }, + { + "name": "CodeFresh", + "url": "${CF_BUILD_URL}", + "buildNumber": "${CF_BUILD_ID}", + "git": { + "remote": "${CF_COMMIT_URL/(.*)\\/commit.+$/\\1}.git", + "revision": "${CF_REVISION}", + "branch": "${CF_BRANCH}" + } + }, + { + "name": "CodeShip", + "url": "${CI_BUILD_URL}", + "buildNumber": "${CI_BUILD_NUMBER}", + "git": { + "remote": "${CI_PULL_REQUEST/(.*)\\/pull\\/\\d+/\\1.git}", + "revision": "${CI_COMMIT_ID}", + "branch": "${CI_BRANCH}" + } + }, + { + "name": "GitHub Actions", + "url": "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}", + "buildNumber": "${GITHUB_RUN_ID}", + "git": { + "remote": "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git", + "revision": "${GITHUB_SHA}", + "branch": "${GITHUB_HEAD_REF}", + "tag": "${GITHUB_REF/refs\/tags\/(.*)/\\1}" + } + }, + { + "name": "GitLab", + "url": "${CI_JOB_URL}", + "buildNumber": "${CI_JOB_ID}", + "git": { + "remote": "${CI_REPOSITORY_URL}", + "revision": "${CI_COMMIT_SHA}", + "branch": "${CI_COMMIT_BRANCH}", + "tag": "${CI_COMMIT_TAG}" + } + }, + { + "name": "GoCD", + "url": "${GO_SERVER_URL}/pipelines/${GO_PIPELINE_NAME}/${GO_PIPELINE_COUNTER}/${GO_STAGE_NAME}/${GO_STAGE_COUNTER}", + "buildNumber": "${GO_PIPELINE_NAME}/${GO_PIPELINE_COUNTER}/${GO_STAGE_NAME}/${GO_STAGE_COUNTER}", + "git": { + "remote": "${GO_SCM_*_PR_URL/(.*)\\/pull\\/\\d+/\\1.git}", + "revision": "${GO_REVISION}", + "branch": "${GO_SCM_*_PR_BRANCH/.*:(.*)/\\1}" + } + }, + { + "name": "Jenkins", + "url": "${BUILD_URL}", + "buildNumber": "${BUILD_NUMBER}", + "git": { + "remote": "${GIT_URL}", + "revision": "${GIT_COMMIT}", + "branch": "${GIT_LOCAL_BRANCH}" + } + }, + { + "name": "Semaphore", + "url": "${SEMAPHORE_ORGANIZATION_URL}/jobs/${SEMAPHORE_JOB_ID}", + "buildNumber": "${SEMAPHORE_JOB_ID}", + "git": { + "remote": "${SEMAPHORE_GIT_URL}", + "revision": "${SEMAPHORE_GIT_SHA}", + "branch": "${SEMAPHORE_GIT_BRANCH}", + "tag": "${SEMAPHORE_GIT_TAG_NAME}" + } + }, + { + "name": "Travis CI", + "url": "${TRAVIS_BUILD_WEB_URL}", + "buildNumber": "${TRAVIS_JOB_NUMBER}", + "git": { + "remote": "https://github.com/${TRAVIS_REPO_SLUG}.git", + "revision": "${TRAVIS_COMMIT}", + "branch": "${TRAVIS_BRANCH}", + "tag": "${TRAVIS_TAG}" + } + }, + { + "name": "Wercker", + "url": "${WERCKER_RUN_URL}", + "buildNumber": "${WERCKER_RUN_URL/.*\\/([^\\/]+)$/\\1}", + "git": { + "remote": "https://${WERCKER_GIT_DOMAIN}/${WERCKER_GIT_OWNER}/${WERCKER_GIT_REPOSITORY}.git", + "revision": "${WERCKER_GIT_COMMIT}", + "branch": "${WERCKER_GIT_BRANCH}" + } + } +] diff --git a/go/Makefile b/go/Makefile new file mode 100644 index 00000000..5de6787c --- /dev/null +++ b/go/Makefile @@ -0,0 +1,16 @@ +FOUND_GO_VERSION := $(shell go version) +EXPECTED_GO_VERSION = 1.17 + +copy-template: + cp ../CiEnvironments.json CiEnvironments.json + +check-go-version: + @$(if $(findstring ${EXPECTED_GO_VERSION}, ${FOUND_GO_VERSION}),(exit 0),(echo Wrong go version! Please install ${EXPECTED_GO_VERSION}; exit 1)) + +test: copy-template check-go-version + @echo "running all tests" + @go install ./... + @go fmt ./... + @go run honnef.co/go/tools/cmd/staticcheck@v0.2.2 github.com/cucumber/ci-environment/go + go vet ./... + go test ./... diff --git a/go/README.md b/go/README.md new file mode 100644 index 00000000..d233ebec --- /dev/null +++ b/go/README.md @@ -0,0 +1,31 @@ +# CI-Environment: Go + +This directory contains the Go implementation of the ci-environment implementation. + +## Template Locations +To embed the templates for the ci-environment go module, we'll use go:embed to include CIEnvironments.json. Since go:embed does not support paths which include `..`, we can't just use a path the the CIEnvironments.json file in the root of the git repository. So a Makefile is included in this go module which will handle copying the most recent template from the root of the git repository to the go directory. The CiEnvironments.json file contains the templates to map available environment variables to values in templates for the variety of supported CI enviroments. + +## Template Expressions + +### Simple Environment Variables +Many are simple replacements of environment variables. E.g, for Azure DevOps, the URI is simply the value of the `${BUILD_BUILDURI}` environment variable. + +### Multiple Environment Variables and/or Constants +For others, it is a combination of multiple enviroment variables and constants. E.g., for Github Actions, the URI is the combination of `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`. + +### Expression Syntax +Still for others, there is some additional processing of values that can be found in environment variables. In these cases, the value in the template consists of three parts: the environment variable to be used, the regex expression (including capture groups) to to used to derive the final value, and the template to be applied using the value(s) from those +capture groups. + +E.g., the Git remote template for CodeShip has a value of `${CI_PULL_REQUEST/(.*)\\/pull\\/\\d+/\\1.git}`. The sections of this expression are delineated by unescaped slashes. So the three parts are the `CI_PULL_REQUEST` environment variable, the regex of `(.*)\\/pull\\/\\d+`, and the template of `\\1.git`. + +So in this example, the URL in the CI_PULL_REQUEST environment variable will have everything prior to `pull/\d+` in a capture group, then the first (and only in this example) capture group is prepended to the literal `.git`. So if the CI_PULL_REQUEST envrionment variable were `https://github.com/owner/repo/pull/42`, then the value of `https://github.com/owner/repo` would be part of that first capture group, and then the resulting value will be `https://github.com/owner/repo.git`. + +Or as explained in ARCHITECTURE.md: +>The expression syntax for environment variables can use the form `${variable/pattern/replacement}`, similar to [bash parameter substitution](https://tldp.org/LDP/abs/html/parameter-substitution.html), but inspired from [sed's s command](https://www.gnu.org/software/sed/manual/html_node/The-_0022s_0022-Command.html) which provides support for capture group back-references in the replacement. + +### Wildcards in Environment Variable Names +In some expressions, an environment variable name can contain a wildcard, e.g., GoCD. The git branch is derived using `GO_SCM_*_PR_BRANCH`. In this situation, the environment variable name contains the branch name (e.g., GO_SCM_MY_MATERIAL_PR_BRANCH where the branch name is MY_MATERIAL) and is thus not a constant. So the wildcard is used as a placeholder and when evaluating the expression, we must loop through all the environment variables until we find the one that matches the pattern. + +## Why Parse the Template Expressions? +In other implementations, such as the javascript implementation for ci-environments, a regex can be used when evaluating the variable/pattern/replacement expressions. An example regex is `'\\${(.*?)(?:(? 0 +} + +// SanitizeGit removes any non-empty Git fields, when the Git.Revision or Git.Remote is empty. +// Standardizes that Git is nil when no Git repository is detected. +// Remove any user info from the git remote value. +func (c *CiEnvironment) SanitizeGit() *CiEnvironment { + if c.Git == nil { + return c + } + if len(c.Git.Remote) == 0 || len(c.Git.Revision) == 0 { + c.Git = nil + } + return c.RemoveUserInfo() +} + +// RemoveUserInfo removes the user info, especially password, from the Git remote URL +func (c *CiEnvironment) RemoveUserInfo() *CiEnvironment { + if c.Git == nil { + return c + } + u, err := url.Parse(c.Git.Remote) + if err != nil { + return c + } + u.User = nil + c.Git.Remote = u.String() + return c +} diff --git a/go/ci_environment_test.go b/go/ci_environment_test.go new file mode 100644 index 00000000..4ac73184 --- /dev/null +++ b/go/ci_environment_test.go @@ -0,0 +1,36 @@ +package cienvironment_test + +import ( + "testing" + + cienvironment "github.com/cucumber/ci-environment/go" + "github.com/stretchr/testify/assert" +) + +func TestRemoveUserInfo(t *testing.T) { + testCases := []struct { + input *cienvironment.CiEnvironment + want *cienvironment.Git + }{ + { + &cienvironment.CiEnvironment{}, + nil, + }, + { + &cienvironment.CiEnvironment{Git: &cienvironment.Git{Revision: "2a2f73c6", Remote: "https://cihost.com"}}, + &cienvironment.Git{Revision: "2a2f73c6", Remote: "https://cihost.com"}, + }, + { + &cienvironment.CiEnvironment{Git: &cienvironment.Git{Revision: "2a2f73c6", Remote: "https://user:pass@cihost.com"}}, + &cienvironment.Git{Revision: "2a2f73c6", Remote: "https://cihost.com"}, + }, + { + &cienvironment.CiEnvironment{Git: &cienvironment.Git{Revision: "2a2f73c6", Remote: "not_a_valid_url"}}, + &cienvironment.Git{Revision: "2a2f73c6", Remote: "not_a_valid_url"}, + }, + } + for _, tc := range testCases { + got := tc.input.RemoveUserInfo().Git + assert.Equal(t, tc.want, got, tc.input.Git) + } +} diff --git a/go/detect_environment.go b/go/detect_environment.go new file mode 100644 index 00000000..8b09b2d4 --- /dev/null +++ b/go/detect_environment.go @@ -0,0 +1,250 @@ +package cienvironment + +import ( + "bufio" + "bytes" + _ "embed" + "encoding/json" + "fmt" + "log" + "os" + "regexp" + "strings" +) + +//go:embed CiEnvironments.json +var ciEnvironmentTemplates string + +func DetectCIEnvironment() *CiEnvironment { + ciEnvironments := []*CiEnvironment{} + + // The Azure template currently only has single back-slashes to escape the slashes in the variable/pattern/replacement + // patterns "${BUILD_SOURCEBRANCH/refs\/heads\/(.*)/\\1}" where others are like "${CI_PULL_REQUEST/(.*)\\/pull\\/\\d+/\\1.git}". + // To make these all consistant prior to parsing, we'll replace those with double back-slashes prior to processing. + re := regexp.MustCompile(`([^\\])\\/`) + template := re.ReplaceAllString(ciEnvironmentTemplates, `${1}\\/`) + err := json.Unmarshal([]byte(template), &ciEnvironments) + + if err != nil { + l := log.New(os.Stderr, "", 0) + l.Printf("error parsing ci templates %s", err) + return nil + } + var environment *CiEnvironment = nil + for _, ciEnvironment := range ciEnvironments { + + // evaluate the expressions for each part of the ci environment configuration + ciEnvironment.URL, _ = evalutate(ciEnvironment.URL) + ciEnvironment.BuildNumber, _ = evalutate(ciEnvironment.BuildNumber) + ciEnvironment.Git.Branch, _ = evalutate(ciEnvironment.Git.Branch) + ciEnvironment.Git.Revision, _ = evalutate(ciEnvironment.Git.Revision) + ciEnvironment.Git.Remote, _ = evalutate(ciEnvironment.Git.Remote) + ciEnvironment.Git.Tag, _ = evalutate(ciEnvironment.Git.Tag) + + if ciEnvironment.IsPresent() { + environment = ciEnvironment + break + } + } + if environment == nil { + return nil + } + return environment.SanitizeGit() +} + +// evaluate each token parsed from the expression and create a value by concatenating all evaluated tokens. +func evalutate(expression string) (string, error) { + result := "" + sc := NewScanner(expression) + tokens := sc.ReadTokens() + for _, token := range tokens { + switch t := token.(type) { + case *variableExpression: + s, err := t.Evaluate() + if err != nil { + return result, err + } + result = fmt.Sprintf("%s%s", result, s) + case *literalExpression: + result = fmt.Sprintf("%s%s", result, t.Value()) + } + } + return result, nil +} + +// any literal or variable expression +type configValue interface { + Value() string +} + +type variableExpression struct { + value string +} + +func (v *variableExpression) Value() string { + return v.value +} + +func (v *variableExpression) Evaluate() (string, error) { + tokens := []string{} + var buf bytes.Buffer + prev := eof + + for _, ch := range v.value { + if ch == '/' && prev != '\\' { + tokens = append(tokens, buf.String()) + buf.Reset() + } else { + buf.WriteRune(ch) + } + prev = ch + } + tokens = append(tokens, buf.String()) + value := "" + matches := [][]string{} + for i, token := range tokens { + switch i { + case 0: + value = getEnv(token) + if len(value) == 0 { + return "", fmt.Errorf("undefined variable: %s", token) + } + case 1: + re, err := regexp.Compile(token) + if err != nil { + return value, fmt.Errorf("error compiling regex: %s", err) + } + matches = re.FindAllStringSubmatch(value, -1) + case 2: + // if no matches exist, then this expression has no value + if len(matches) == 0 { + value = "" + } + for _, match := range matches { + for groupIdx, group := range match { + if groupIdx == 0 { + continue + } + placeHolder := fmt.Sprintf("\\%d", groupIdx) + value = strings.Replace(token, placeHolder, group, -1) + } + } + } + } + + return value, nil +} + +// getEnv return the environment variable if no wildcard is included in the variable name. If the variable name +// does contain a wildcard, then convert to a valid regex and check all envrionemtn variables until we find one that +// matches, if a match does exist. +func getEnv(name string) string { + if strings.Contains(name, "*") { + re := regexp.MustCompile(strings.Replace(name, "*", ".*", -1)) + for _, element := range os.Environ() { + variable := strings.Split(element, "=") + if re.MatchString(variable[0]) { + return variable[1] + } + } + } + return os.Getenv(name) +} + +type literalExpression struct { + value string +} + +func (l *literalExpression) Value() string { + return l.value +} + +type Scanner struct { + r *bufio.Reader +} + +func NewScanner(expression string) Scanner { + reader := bufio.NewReader(strings.NewReader(expression)) + return Scanner{ + r: reader, + } +} + +var eof = rune(0) + +// ReadTokens returns an array of all literal and variable tokens found in the expression being scanned +func (s Scanner) ReadTokens() []configValue { + tokens := []configValue{} + for { + token := s.Next() + if token == nil { + break + } + tokens = append(tokens, token) + } + return tokens +} + +// Next returns the next token from the scanner +func (s Scanner) Next() configValue { + ch := s.read() + if ch == eof { + return nil + } + switch ch { + case '$': + peek := s.read() + if peek == '{' { + return s.readVariableExpression() + } else { + s.unread() + return s.readLiteral() + } + } + s.unread() + return s.readLiteral() +} + +func (s Scanner) readVariableExpression() *variableExpression { + expression := &variableExpression{} + var buf bytes.Buffer + for { + if ch := s.read(); ch == eof || ch == '}' { + expression.value = buf.String() + break + } else { + buf.WriteRune(ch) + } + } + return expression +} + +func (s Scanner) readLiteral() *literalExpression { + literal := &literalExpression{} + var buf bytes.Buffer + for { + if ch := s.read(); ch == eof { + literal.value = buf.String() + break + } else if ch == '$' { + s.unread() + literal.value = buf.String() + break + } else { + buf.WriteRune(ch) + } + } + return literal +} + +func (s Scanner) read() rune { + ch, _, err := s.r.ReadRune() + if err != nil { + return eof + } + return ch +} + +func (s Scanner) unread() { + s.r.UnreadRune() +} diff --git a/go/detect_environment_test.go b/go/detect_environment_test.go new file mode 100644 index 00000000..f410f31b --- /dev/null +++ b/go/detect_environment_test.go @@ -0,0 +1,108 @@ +package cienvironment_test + +import ( + "bufio" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "strings" + "testing" + + cienvironment "github.com/cucumber/ci-environment/go" + "github.com/stretchr/testify/assert" +) + +type testCase struct { + fileName string + envVars map[string]string + want *cienvironment.CiEnvironment +} + +func TestDetectCIEnvironment_AcceptanceTests(t *testing.T) { + testCases := loadTestData() + for _, tc := range testCases { + t.Run(tc.fileName, testDetectCIEnvironment(tc.envVars, tc.want)) + } +} + +func testDetectCIEnvironment(envVars map[string]string, want *cienvironment.CiEnvironment) func(*testing.T) { + return func(t *testing.T) { + // when running on github, unset the variables already present there for this test execution + t.Setenv("GITHUB_SERVER_URL", "") + t.Setenv("GITHUB_REPOSITORY", "") + t.Setenv("GITHUB_RUN_ID", "") + t.Setenv("GITHUB_SHA", "") + t.Setenv("GITHUB_HEAD_REF", "") + t.Setenv("GITHUB_EVENT_NAME", "") + t.Setenv("GITHUB_EVENT_PATH", "") + // set the environment variables per the .txt file in the testdata directory + for k, v := range envVars { + t.Setenv(k, v) + } + got := cienvironment.DetectCIEnvironment() + assert.Equal(t, want.Name, got.Name) + assert.Equal(t, want.URL, got.URL, "URL for %s", want.Name) + assert.Equal(t, want.BuildNumber, got.BuildNumber, "BuildNumber for %s", want.Name) + if want.Git == nil { + assert.Nil(t, got.Git, "Git for %s", want.Name) + } else { + assert.Equal(t, want.Git.Branch, got.Git.Branch, "Git.Branch for %s", want.Name) + assert.Equal(t, want.Git.Remote, got.Git.Remote, "Git.Remote for %s", want.Name) + assert.Equal(t, want.Git.Tag, got.Git.Tag, "Git.Tag for %s", want.Name) + // In the acceptance test files (../testdata), for GitHubActionsPullRequestOpened and GitHubActionsPullRequestSynchronize, + // the .txt file has a GITHUB_SHA of 99684bcacf01d95875834d87903dcb072306c9ad, but the json files have expected + // git.revision values of d79b417f8e974b9a2d5e6483845a96446695f944 and 3738117e3337e54955580f4e98cf767d96b42135, respectively. + // The template is "revision": "${GITHUB_SHA}", + if want.Name != "GitHub Actions" { + assert.Equal(t, want.Git.Revision, got.Git.Revision, "Git.Revision for %s", want.Name) + } + } + } +} + +func loadTestData() []testCase { + baseDir := "../testdata" + files, err := ioutil.ReadDir(baseDir) + if err != nil { + log.Fatal(err) + } + testCases := []testCase{} + for _, file := range files { + if !file.IsDir() && strings.HasSuffix(file.Name(), ".txt") { + testCase := testCase{ + fileName: file.Name(), + envVars: map[string]string{}, + } + testDataFile := fmt.Sprintf("%s/%s", baseDir, file.Name()) + readFile, err := os.Open(testDataFile) + if err != nil { + fmt.Println(err) + } + fileScanner := bufio.NewScanner(readFile) + fileScanner.Split(bufio.ScanLines) + for fileScanner.Scan() { + line := fileScanner.Text() + tokens := strings.SplitN(line, "=", 2) + if len(tokens) > 1 { + testCase.envVars[tokens[0]] = tokens[1] + } + readFile.Close() + } + fileContent, err := ioutil.ReadFile(fmt.Sprintf("%s.json", testDataFile)) + if err != nil { + log.Fatal(err) + } + ciEnvironment := &cienvironment.CiEnvironment{} + err = json.Unmarshal([]byte(fileContent), &ciEnvironment) + if err != nil { + l := log.New(os.Stderr, "", 0) + l.Printf("error parsing ci templates %s", err) + } + testCase.want = ciEnvironment.SanitizeGit() + testCases = append(testCases, testCase) + } + } + return testCases +} diff --git a/go/go.mod b/go/go.mod new file mode 100644 index 00000000..09b9b998 --- /dev/null +++ b/go/go.mod @@ -0,0 +1,11 @@ +module github.com/cucumber/ci-environment/go + +go 1.17 + +require github.com/stretchr/testify v1.8.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go/go.sum b/go/go.sum new file mode 100644 index 00000000..51648299 --- /dev/null +++ b/go/go.sum @@ -0,0 +1,15 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=