├── .chglog ├── CHANGELOG.tpl.md └── config.yml ├── .github ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── README.md ├── SUPPORT.md ├── renovate.json └── workflows │ ├── codeql-analysis.yml │ ├── release.yml │ ├── reviewdog.yml │ ├── test.yml │ └── validate.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── NOTICE ├── codecov.yml ├── compiler ├── context.go ├── context_test.go ├── doc.go ├── engine.go └── native │ ├── clone.go │ ├── clone_test.go │ ├── compile.go │ ├── compile_test.go │ ├── doc.go │ ├── environment.go │ ├── environment_test.go │ ├── expand.go │ ├── expand_test.go │ ├── initialize.go │ ├── initialize_test.go │ ├── native.go │ ├── native_test.go │ ├── parse.go │ ├── parse_test.go │ ├── script.go │ ├── script_test.go │ ├── substitute.go │ ├── substitute_test.go │ ├── testdata │ ├── clone_false.yml │ ├── clone_replace.yml │ ├── clone_true.yml │ ├── invalid.yml │ ├── invalid_type.yml │ ├── metadata.yml │ ├── parameters.yml │ ├── pipeline_type.star │ ├── pipeline_type_default.yml │ ├── pipeline_type_go.yml │ ├── secrets.yml │ ├── stages.yml │ ├── stages_pipeline.yml │ ├── stages_pipeline_template.yml │ ├── steps.yml │ ├── steps_and_stages.yml │ ├── steps_pipeline.yml │ ├── steps_pipeline_template.yml │ ├── template-gradle.json │ ├── template-maven.json │ ├── template-starlark.json │ ├── template.json │ ├── template.star │ └── template.yml │ ├── transform.go │ ├── transform_test.go │ ├── validate.go │ └── validate_test.go ├── go.mod ├── go.sum ├── registry ├── doc.go ├── github │ ├── doc.go │ ├── github.go │ ├── github_test.go │ ├── parse.go │ ├── parse_test.go │ ├── template.go │ ├── template_test.go │ └── testdata │ │ ├── template.json │ │ └── template.yml ├── registry.go └── source.go └── template ├── doc.go ├── native ├── convert.go ├── convert_test.go ├── doc.go ├── render.go ├── render_test.go └── testdata │ ├── build │ ├── basic │ │ ├── build.yml │ │ └── want.yml │ ├── basic_stages │ │ ├── build.yml │ │ └── want.yml │ └── conditional │ │ ├── build.yml │ │ └── want.yml │ └── step │ ├── basic │ ├── step.yml │ ├── tmpl.yml │ └── want.yml │ ├── conditional │ ├── step.yml │ ├── tmpl.yml │ └── want.yml │ ├── disallowed │ ├── tmpl_env.yml │ └── tmpl_expandenv.yml │ ├── invalid.yml │ ├── invalid_template.yml │ ├── invalid_variables.yml │ ├── loop_map │ ├── step.yml │ ├── tmpl.yml │ └── want.yml │ ├── loop_slice │ ├── step.yml │ ├── tmpl.yml │ └── want.yml │ ├── multiline │ ├── step.yml │ ├── tmpl.yml │ └── want.yml │ └── with_vars_plat │ ├── step.yml │ ├── tmpl.yml │ └── want.yml ├── starlark ├── convert.go ├── convert_test.go ├── render.go ├── render_test.go ├── starlark.go ├── starlark_test.go └── testdata │ ├── build │ ├── basic │ │ ├── build.star │ │ └── want.yml │ ├── basic_stages │ │ ├── build.star │ │ └── want.yml │ └── conditional │ │ ├── build.star │ │ └── want.yml │ └── step │ ├── basic │ ├── step.yml │ ├── template.py │ └── want.yml │ ├── cancel │ ├── step.yml │ └── template.star │ ├── with_method │ ├── step.yml │ ├── template.star │ └── want.yml │ ├── with_vars │ ├── step.yml │ ├── template.star │ └── want.yml │ └── with_vars_plat │ ├── step.yml │ ├── template.star │ └── want.yml └── template.go /.chglog/CHANGELOG.tpl.md: -------------------------------------------------------------------------------- 1 | {{ range .Versions }} 2 | 3 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }}) 4 | 5 | {{ range .CommitGroups -}} 6 | ### {{ .Title }} 7 | 8 | {{ range .Commits -}} 9 | * {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} 10 | {{ end }} 11 | {{ end -}} 12 | 13 | {{- if .RevertCommits -}} 14 | ### Reverts 15 | 16 | {{ range .RevertCommits -}} 17 | * {{ .Revert.Header }} 18 | {{ end }} 19 | {{ end -}} 20 | 21 | {{- if .NoteGroups -}} 22 | {{ range .NoteGroups -}} 23 | ### {{ .Title }} 24 | 25 | {{ range .Notes }} 26 | {{ .Body }} 27 | {{ end }} 28 | {{ end -}} 29 | {{ end -}} 30 | {{ end -}} -------------------------------------------------------------------------------- /.chglog/config.yml: -------------------------------------------------------------------------------- 1 | style: github 2 | template: CHANGELOG.tpl.md 3 | info: 4 | title: CHANGELOG 5 | repository_url: https://github.com/go-vela/compiler 6 | options: 7 | commits: 8 | # filters: 9 | # Type: 10 | # - feat 11 | # - fix 12 | # - perf 13 | # - refactor 14 | commit_groups: 15 | # title_maps: 16 | # feat: Features 17 | # fix: Bug Fixes 18 | # perf: Performance Improvements 19 | # refactor: Code Refactoring 20 | header: 21 | pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$" 22 | pattern_maps: 23 | - Type 24 | - Scope 25 | - Subject 26 | notes: 27 | keywords: 28 | - BREAKING CHANGE -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in the repo. 2 | * @go-vela/admins 3 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at 59 | [TTS-OpenSource-Office@target.com](mailto:TTS-OpenSource-Office@target.com). All 60 | complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 73 | 74 | [homepage]: https://www.contributor-covenant.org 75 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | > DISCLAIMER: 4 | > 5 | > The contents of this repository have been migrated into [go-vela/server](https://github.com/go-vela/server). 6 | > 7 | > This was done as a part of [go-vela/community#394](https://github.com/go-vela/community/issues/394) to deliver [on a proposal](https://github.com/go-vela/community/blob/master/proposals/2021/08-25_repo-structure.md). 8 | 9 | We'd love to accept your contributions to this project! There are just a few guidelines you need to follow. 10 | 11 | ## Bugs 12 | 13 | Bug reports should be opened up as [issues](https://help.github.com/en/github/managing-your-work-on-github/about-issues) on the [go-vela/community](https://github.com/go-vela/community) repository! 14 | 15 | ## Feature Requests 16 | 17 | Feature Requests should be opened up as [issues](https://help.github.com/en/github/managing-your-work-on-github/about-issues) on the [go-vela/community](https://github.com/go-vela/community) repository! 18 | 19 | ## Pull Requests 20 | 21 | **NOTE: We recommend you start by opening a new issue describing the bug or feature you're intending to fix. Even if you think it's relatively minor, it's helpful to know what people are working on.** 22 | 23 | We are always open to new PRs! You can follow the below guide for learning how you can contribute to the project! 24 | 25 | ## Getting Started 26 | 27 | ### Prerequisites 28 | 29 | * [Review the commit guide we follow](https://chris.beams.io/posts/git-commit/#seven-rules) - ensure your commits follow our standards 30 | * [Golang](https://golang.org/dl/) - for source code and [dependency management](https://github.com/golang/go/wiki/Modules) 31 | 32 | ### Setup 33 | 34 | * [Fork](/fork) this repository 35 | 36 | * Clone this repository to your workstation: 37 | 38 | ```bash 39 | # Clone the project 40 | git clone git@github.com:go-vela/compiler.git $HOME/go-vela/compiler 41 | ``` 42 | 43 | * Navigate to the repository code: 44 | 45 | ```bash 46 | # Change into the project directory 47 | cd $HOME/go-vela/compiler 48 | ``` 49 | 50 | * Point the original code at your fork: 51 | 52 | ```bash 53 | # Add a remote branch pointing to your fork 54 | git remote add fork https://github.com/your_fork/compiler 55 | ``` 56 | 57 | ### Development 58 | 59 | * Navigate to the repository code: 60 | 61 | ```bash 62 | # Change into the project directory 63 | cd $HOME/go-vela/compiler 64 | ``` 65 | 66 | * Write your code 67 | * Please be sure to [follow our commit rules](https://chris.beams.io/posts/git-commit/#seven-rules) 68 | * Please address linter warnings appropriately. If you are intentionally violating a rule that triggers a linter, please annotate the respective code with `nolint` declarations [[docs](https://golangci-lint.run/usage/false-positives/)]. we are using the following format for `nolint` declarations: 69 | 70 | ```go 71 | // nolint: // 72 | ``` 73 | 74 | Example: 75 | 76 | ```go 77 | // nolint:gocyclo // legacy function is complex, needs simplification 78 | func superComplexFunction() error { 79 | // .. 80 | } 81 | ``` 82 | 83 | Check the [documentation for more examples](https://golangci-lint.run/usage/false-positives/). 84 | 85 | * Write tests for your changes and ensure they pass: 86 | 87 | ```bash 88 | # Test the code with `go` 89 | go test ./... 90 | ``` 91 | 92 | * Ensure your code meets the project standards: 93 | 94 | ```bash 95 | # Clean the code with `go` 96 | go mod tidy 97 | go fmt ./... 98 | go vet ./... 99 | ``` 100 | 101 | * Push to your fork: 102 | 103 | ```bash 104 | # Push your code up to your fork 105 | git push fork master 106 | ``` 107 | 108 | * Open a pull request! 109 | * For the title of the pull request, please use the following format for the title: 110 | 111 | ```text 112 | feat(wobble): add hat wobble 113 | ^--^^------^ ^------------^ 114 | | | | 115 | | | +---> Summary in present tense. 116 | | +---> Scope: a noun describing a section of the codebase (optional) 117 | +---> Type: chore, docs, feat, fix, refactor, or test. 118 | ``` 119 | 120 | * feat: adds a new feature (equivalent to a MINOR in Semantic Versioning) 121 | * fix: fixes a bug (equivalent to a PATCH in Semantic Versioning) 122 | * docs: changes to the documentation 123 | * refactor: refactors production code, eg. renaming a variable; doesn't change public API 124 | * test: adds missing tests, refactors tests; no production code change 125 | * chore: updates something without impacting the user (ex: bump a dependency in package.json or go.mod); no production code change 126 | 127 | If a code change introduces a breaking change, place ! suffix after type, ie. feat(change)!: adds breaking change. correlates with MAJOR in semantic versioning. 128 | 129 | Thank you for your contribution! 130 | -------------------------------------------------------------------------------- /.github/README.md: -------------------------------------------------------------------------------- 1 | # compiler 2 | 3 | > DISCLAIMER: 4 | > 5 | > The contents of this repository have been migrated into [go-vela/server](https://github.com/go-vela/server). 6 | > 7 | > This was done as a part of [go-vela/community#394](https://github.com/go-vela/community/issues/394) to deliver [on a proposal](https://github.com/go-vela/community/blob/master/proposals/2021/08-25_repo-structure.md). 8 | 9 | [![license](https://img.shields.io/crates/l/gl.svg)](../LICENSE) 10 | [![GoDoc](https://godoc.org/github.com/go-vela/compiler?status.svg)](https://godoc.org/github.com/go-vela/compiler) 11 | [![Go Report Card](https://goreportcard.com/badge/go-vela/compiler)](https://goreportcard.com/report/go-vela/compiler) 12 | [![codecov](https://codecov.io/gh/go-vela/compiler/branch/master/graph/badge.svg)](https://codecov.io/gh/go-vela/compiler) 13 | 14 | > Vela is in active development and is a pre-release product. 15 | > 16 | > Feel free to send us feedback at https://github.com/go-vela/community/issues/new. 17 | 18 | Vela is a Pipeline Automation (CI/CD) framework built on [Linux container](https://linuxcontainers.org/) technology written in [Golang](https://golang.org/). 19 | 20 | Vela uses a syntax similar to [Docker Compose](https://docs.docker.com/compose/) to define its configuration. This structure for repeated use, within the application, is called a pipeline and a single execution of a pipeline is referenced as a build. 21 | 22 | ## Documentation 23 | 24 | For installation and usage, please [visit our docs](https://go-vela.github.io/docs). 25 | 26 | ## Contributing 27 | 28 | We are always welcome to new pull requests! 29 | 30 | Please see our [contributing](CONTRIBUTING.md) documentation for further instructions. 31 | 32 | ## Support 33 | 34 | We are always here to help! 35 | 36 | Please see our [support](SUPPORT.md) documentation for further instructions. 37 | 38 | ## Copyright and License 39 | 40 | ``` 41 | Copyright (c) 2021 Target Brands, Inc. 42 | ``` 43 | 44 | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 45 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | Welcome to Vela! To get help with a specific aspect of Vela, we've provided the below information. 4 | 5 | ## Bugs or Feature Requests 6 | 7 | We use GitHub for tracking bugs and feature requests. 8 | 9 | Please see our [contributing](CONTRIBUTING.md) documentation for further instructions. 10 | 11 | ## Installation and Usage 12 | 13 | We use GitHub pages for hosting our installation and usage documentation. 14 | 15 | Please see our [documentation](https://go-vela.github.io/docs) site for more information. 16 | 17 | ## Questions 18 | 19 | We use Slack for supporting questions not already covered in the above documents. 20 | 21 | Please join the [#vela](https://gophers.slack.com/app_redirect?channel=CNRRKE8KY) channel. 22 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>go-vela/renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '29 4 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'go' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # name of the action 2 | name: release 3 | 4 | # trigger on push events with `v*` in tag 5 | # ignore push events with `v*-rc*` in tag 6 | on: 7 | push: 8 | tags: 9 | - 'v*' 10 | - '!v*-rc*' 11 | 12 | # pipeline to execute 13 | jobs: 14 | release: 15 | runs-on: ubuntu-latest 16 | container: 17 | image: golang:1.16 18 | steps: 19 | - name: clone 20 | uses: actions/checkout@v2 21 | 22 | - name: tags 23 | run: | 24 | git fetch --tags 25 | 26 | - name: version 27 | id: version 28 | run: | 29 | echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} 30 | 31 | - name: install 32 | run: | 33 | go get github.com/git-chglog/git-chglog/cmd/git-chglog 34 | go get github.com/github-release/github-release 35 | 36 | - name: changelog 37 | run: | 38 | # https://github.com/git-chglog/git-chglog#git-chglog 39 | $(go env GOPATH)/bin/git-chglog \ 40 | -o $GITHUB_WORKSPACE/CHANGELOG.md \ 41 | ${{ steps.version.outputs.VERSION }} 42 | 43 | - name: release 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | run: | 47 | # https://github.com/github-release/github-release#how-to-use 48 | $(go env GOPATH)/bin/github-release edit \ 49 | --user go-vela \ 50 | --repo compiler \ 51 | --tag ${{ steps.version.outputs.VERSION }} \ 52 | --name ${{ steps.version.outputs.VERSION }} \ 53 | --description "$(cat $GITHUB_WORKSPACE/CHANGELOG.md)" 54 | -------------------------------------------------------------------------------- /.github/workflows/reviewdog.yml: -------------------------------------------------------------------------------- 1 | # name of the action 2 | name: reviewdog 3 | 4 | # trigger on pull_request events 5 | on: 6 | pull_request: 7 | 8 | # pipeline to execute 9 | jobs: 10 | diff-review: 11 | runs-on: ubuntu-latest 12 | container: 13 | image: golang:1.16 14 | steps: 15 | - name: clone 16 | uses: actions/checkout@v2 17 | 18 | - name: golangci-lint 19 | uses: reviewdog/action-golangci-lint@v2 20 | with: 21 | github_token: ${{ secrets.github_token }} 22 | golangci_lint_flags: "--config=.golangci.yml" 23 | fail_on_error: true 24 | filter_mode: diff_context 25 | reporter: github-pr-review 26 | 27 | full-review: 28 | runs-on: ubuntu-latest 29 | container: 30 | image: golang:1.16 31 | steps: 32 | - name: clone 33 | uses: actions/checkout@v2 34 | 35 | - name: golangci-lint 36 | uses: reviewdog/action-golangci-lint@v2 37 | with: 38 | github_token: ${{ secrets.github_token }} 39 | golangci_lint_flags: "--config=.golangci.yml" 40 | fail_on_error: false 41 | filter_mode: nofilter 42 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # name of the action 2 | name: test 3 | 4 | # trigger on pull_request or push events 5 | on: 6 | pull_request: 7 | push: 8 | 9 | # pipeline to execute 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | container: 14 | image: golang:1.16 15 | steps: 16 | - name: clone 17 | uses: actions/checkout@v2 18 | 19 | - name: install 20 | run: | 21 | go get github.com/mattn/goveralls 22 | 23 | - name: test 24 | run: | 25 | go test -race -covermode=atomic -coverprofile=coverage.out ./... 26 | 27 | - name: coverage 28 | uses: codecov/codecov-action@v2 29 | with: 30 | token: ${{ secrets.CODECOV_TOKEN }} 31 | file: coverage.out 32 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | # name of the action 2 | name: validate 3 | 4 | # trigger on pull_request or push events 5 | on: 6 | pull_request: 7 | push: 8 | 9 | # pipeline to execute 10 | jobs: 11 | validate: 12 | runs-on: ubuntu-latest 13 | container: 14 | image: golang:1.16 15 | steps: 16 | - name: clone 17 | uses: actions/checkout@v2 18 | 19 | - name: validate 20 | run: | 21 | # Check that go mod tidy produces a zero diff; clean up any changes afterwards. 22 | go mod tidy && git diff --exit-code; code=$?; git checkout -- .; (exit $code) 23 | # Check that go vet ./... produces a zero diff; clean up any changes afterwards. 24 | go vet ./... && git diff --exit-code; code=$?; git checkout -- .; (exit $code) 25 | # Check that go fmt ./... produces a zero diff; clean up any changes afterwards. 26 | go fmt ./... && git diff --exit-code; code=$?; git checkout -- .; (exit $code) 27 | # Check that go fix ./... produces a zero diff; clean up any changes afterwards. 28 | go fix ./... && git diff --exit-code; code=$?; git checkout -- .; (exit $code) 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/go 2 | 3 | ### Go ### 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | ### Go Patch ### 18 | /vendor/ 19 | /Godeps/ 20 | 21 | # End of https://www.gitignore.io/api/go 22 | 23 | # IntelliJ project folder 24 | .idea/ 25 | 26 | # IntelliJ project files 27 | *.iml 28 | *.ipr 29 | *.iws 30 | *.xml 31 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # This is a manually created golangci.com yaml configuration with 2 | # some defaults explicitly provided. There is a large number of 3 | # linters we've enabled that are usually disabled by default. 4 | # 5 | # https://golangci-lint.run/usage/configuration/#config-file 6 | 7 | # This section provides the configuration for how golangci 8 | # outputs it results from the linters it executes. 9 | output: 10 | # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" 11 | format: colored-line-number 12 | 13 | # print lines of code with issue, default is true 14 | print-issued-lines: true 15 | 16 | # print linter name in the end of issue text, default is true 17 | print-linter-name: true 18 | 19 | # make issues output unique by line, default is true 20 | uniq-by-line: true 21 | 22 | # This section provides the configuration for each linter 23 | # we've instructed golangci to execute. 24 | linters-settings: 25 | # https://github.com/mibk/dupl 26 | dupl: 27 | threshold: 100 28 | 29 | # https://github.com/ultraware/funlen 30 | funlen: 31 | lines: 100 32 | statements: 50 33 | 34 | # https://github.com/tommy-muehle/go-mnd 35 | gomnd: 36 | settings: 37 | mnd: 38 | # don't include the "operation" and "assign" 39 | checks: argument,case,condition,return 40 | 41 | # https://github.com/walle/lll 42 | lll: 43 | line-length: 100 44 | 45 | # https://github.com/mdempsky/maligned 46 | maligned: 47 | suggest-new: true 48 | 49 | # https://github.com/client9/misspell 50 | misspell: 51 | locale: US 52 | 53 | # https://github.com/golangci/golangci-lint/blob/master/pkg/golinters/nolintlint 54 | nolintlint: 55 | allow-leading-space: true # allow non-"machine-readable" format (ie. with leading space) 56 | allow-unused: false # allow nolint directives that don't address a linting issue 57 | require-explanation: true # require an explanation for nolint directives 58 | require-specific: true # require nolint directives to be specific about which linter is being skipped 59 | 60 | # This section provides the configuration for which linters 61 | # golangci will execute. Several of them were disabled by 62 | # default but we've opted to enable them. 63 | linters: 64 | # disable all linters as new linters might be added to golangci 65 | disable-all: true 66 | 67 | # enable a specific set of linters to run 68 | enable: 69 | - bodyclose 70 | - deadcode # enabled by default 71 | - dupl 72 | - errcheck # enabled by default 73 | - funlen 74 | - goconst 75 | - gocyclo 76 | - godot 77 | - gofmt 78 | - goimports 79 | - revive 80 | - gomnd 81 | - goprintffuncname 82 | - gosec 83 | - gosimple # enabled by default 84 | - govet # enabled by default 85 | - ineffassign # enabled by default 86 | - lll 87 | - misspell 88 | - nakedret 89 | - nolintlint 90 | - staticcheck # enabled by default 91 | - structcheck # enabled by default 92 | - stylecheck 93 | - typecheck # enabled by default 94 | - unconvert 95 | - unparam 96 | - unused # enabled by default 97 | - varcheck # enabled by default 98 | - whitespace 99 | 100 | # static list of linters we know golangci can run but we've 101 | # chosen to leave disabled for now 102 | # - asciicheck 103 | # - depguard 104 | # - dogsled 105 | # - exhaustive 106 | # - gochecknoinits 107 | # - gochecknoglobals 108 | # - gocognit 109 | # - gocritic 110 | # - godox 111 | # - goerr113 112 | # - interfacer 113 | # - nestif 114 | # - noctx 115 | # - prealloc 116 | # - rowserrcheck 117 | # - scopelint 118 | # - testpackage 119 | # - wsl 120 | 121 | # This section provides the configuration for how golangci 122 | # will report the issues it finds. 123 | issues: 124 | # Excluding configuration per-path, per-linter, per-text and per-source 125 | exclude-rules: 126 | # prevent linters from running on *_test.go files 127 | - path: _test\.go 128 | linters: 129 | - dupl 130 | - funlen 131 | - goconst 132 | - gocyclo 133 | - gomnd 134 | - lll 135 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | # 3 | # Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | # The `clean` target is intended to clean the workspace 6 | # and prepare the local changes for submission. 7 | # 8 | # Usage: `make clean` 9 | .PHONY: clean 10 | clean: tidy vet fmt fix 11 | 12 | # The `tidy` target is intended to clean up 13 | # the Go module files (go.mod & go.sum). 14 | # 15 | # Usage: `make tidy` 16 | .PHONY: tidy 17 | tidy: 18 | @echo 19 | @echo "### Tidying Go module" 20 | @go mod tidy 21 | 22 | # The `vet` target is intended to inspect the 23 | # Go source code for potential issues. 24 | # 25 | # Usage: `make vet` 26 | .PHONY: vet 27 | vet: 28 | @echo 29 | @echo "### Vetting Go code" 30 | @go vet ./... 31 | 32 | # The `fmt` target is intended to format the 33 | # Go source code to meet the language standards. 34 | # 35 | # Usage: `make fmt` 36 | .PHONY: fmt 37 | fmt: 38 | @echo 39 | @echo "### Formatting Go Code" 40 | @go fmt ./... 41 | 42 | # The `fix` target is intended to rewrite the 43 | # Go source code using old APIs. 44 | # 45 | # Usage: `make fix` 46 | .PHONY: fix 47 | fix: 48 | @echo 49 | @echo "### Fixing Go Code" 50 | @go fix ./... 51 | 52 | # The `test` target is intended to run 53 | # the tests for the Go source code. 54 | # 55 | # Usage: `make test` 56 | .PHONY: test 57 | test: 58 | @echo 59 | @echo "### Testing Go Code" 60 | @go test -race ./... 61 | 62 | # The `test-cover` target is intended to run 63 | # the tests for the Go source code and then 64 | # open the test coverage report. 65 | # 66 | # Usage: `make test-cover` 67 | .PHONY: test-cover 68 | test-cover: 69 | @echo 70 | @echo "### Creating test coverage report" 71 | @go test -race -covermode=atomic -coverprofile=coverage.out ./... 72 | @echo 73 | @echo "### Opening test coverage report" 74 | @go tool cover -html=coverage.out 75 | 76 | # The `check` target is intended to output all 77 | # dependencies from the Go module that need updates. 78 | # 79 | # Usage: `make check` 80 | .PHONY: check 81 | check: check-install 82 | @echo 83 | @echo "### Checking dependencies for updates" 84 | @go list -u -m -json all | go-mod-outdated -update 85 | 86 | # The `check-direct` target is intended to output direct 87 | # dependencies from the Go module that need updates. 88 | # 89 | # Usage: `make check-direct` 90 | .PHONY: check-direct 91 | check-direct: check-install 92 | @echo 93 | @echo "### Checking direct dependencies for updates" 94 | @go list -u -m -json all | go-mod-outdated -direct 95 | 96 | # The `check-full` target is intended to output 97 | # all dependencies from the Go module. 98 | # 99 | # Usage: `make check-full` 100 | .PHONY: check-full 101 | check-full: check-install 102 | @echo 103 | @echo "### Checking all dependencies for updates" 104 | @go list -u -m -json all | go-mod-outdated 105 | 106 | # The `check-install` target is intended to download 107 | # the tool used to check dependencies from the Go module. 108 | # 109 | # Usage: `make check-install` 110 | .PHONY: check-install 111 | check-install: 112 | @echo 113 | @echo "### Installing psampaz/go-mod-outdated" 114 | @go get -u github.com/psampaz/go-mod-outdated 115 | 116 | # The `bump-deps` target is intended to upgrade 117 | # non-test dependencies for the Go module. 118 | # 119 | # Usage: `make bump-deps` 120 | .PHONY: bump-deps 121 | bump-deps: check 122 | @echo 123 | @echo "### Upgrading dependencies" 124 | @go get -u ./... 125 | 126 | # The `bump-deps-full` target is intended to upgrade 127 | # all dependencies for the Go module. 128 | # 129 | # Usage: `make bump-deps-full` 130 | .PHONY: bump-deps-full 131 | bump-deps-full: check 132 | @echo 133 | @echo "### Upgrading all dependencies" 134 | @go get -t -u ./... 135 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | # The is the default codecov.io yaml configuration all projects use 2 | # that do not have their own codecov.yml already in the project. 3 | # 4 | # https://docs.codecov.io/docs/codecov-yaml#section-default-yaml 5 | 6 | # This section provides the generic configuration for codecov. 7 | # 8 | # https://docs.codecov.io/docs/codecovyml-reference#section-codecov 9 | codecov: 10 | 11 | require_ci_to_pass: yes 12 | 13 | # This section provides the configuration for the 14 | # coverage report codecov analyzes for results. 15 | # 16 | # https://docs.codecov.io/docs/codecovyml-reference#section-coverage 17 | coverage: 18 | 19 | precision: 2 20 | round: down 21 | range: "70...100" 22 | 23 | # https://docs.codecov.io/docs/commit-status 24 | status: 25 | # set rules for the project status report 26 | project: 27 | default: 28 | target: 90% 29 | threshold: 0% 30 | # disable the patch status report 31 | patch: off 32 | # disable the changes status report 33 | changes: off 34 | 35 | # This section provides the configuration for the 36 | # parsers codecov uses for the coverage report. 37 | # 38 | # https://docs.codecov.io/docs/codecovyml-reference#section-parsers 39 | parsers: 40 | 41 | gcov: 42 | 43 | branch_detection: 44 | conditional: yes 45 | loop: yes 46 | method: no 47 | macro: no 48 | 49 | # This section provides the configuration for the 50 | # comments codecov makes to open pull requests. 51 | # 52 | # https://docs.codecov.io/docs/codecovyml-reference#section-comment 53 | comment: 54 | 55 | layout: "reach, diff, flags, files" 56 | behavior: default 57 | require_changes: no 58 | -------------------------------------------------------------------------------- /compiler/context.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package compiler 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | // key defines the key type for storing 14 | // the compiler Engine in the context. 15 | const key = "compiler" 16 | 17 | // FromContext retrieves the compiler Engine from the context.Context. 18 | func FromContext(c context.Context) Engine { 19 | // get compiler value from context.Context 20 | v := c.Value(key) 21 | if v == nil { 22 | return nil 23 | } 24 | 25 | // cast compiler value to expected Engine type 26 | e, ok := v.(Engine) 27 | if !ok { 28 | return nil 29 | } 30 | 31 | return e 32 | } 33 | 34 | // FromGinContext retrieves the compiler Engine from the gin.Context. 35 | func FromGinContext(c *gin.Context) Engine { 36 | // get compiler value from gin.Context 37 | // 38 | // https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc#Context.Get 39 | v, ok := c.Get(key) 40 | if !ok { 41 | return nil 42 | } 43 | 44 | // cast compiler value to expected Engine type 45 | e, ok := v.(Engine) 46 | if !ok { 47 | return nil 48 | } 49 | 50 | return e 51 | } 52 | 53 | // WithContext inserts the compiler Engine into the context.Context. 54 | func WithContext(c context.Context, e Engine) context.Context { 55 | // set the compiler Engine in the context.Context 56 | // 57 | // nolint: revive,staticcheck // ignore using string with context value 58 | return context.WithValue(c, key, e) 59 | } 60 | 61 | // WithGinContext inserts the compiler Engine into the gin.Context. 62 | func WithGinContext(c *gin.Context, e Engine) { 63 | // set the compiler Engine in the gin.Context 64 | // 65 | // https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc#Context.Set 66 | c.Set(key, e) 67 | } 68 | -------------------------------------------------------------------------------- /compiler/context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package compiler 6 | 7 | import ( 8 | "context" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func TestCompiler_FromContext(t *testing.T) { 16 | // setup types 17 | var _engine Engine 18 | 19 | // setup tests 20 | tests := []struct { 21 | context context.Context 22 | want Engine 23 | }{ 24 | { 25 | // nolint: staticcheck // ignore using string with context value 26 | context: context.WithValue(context.Background(), key, _engine), 27 | want: _engine, 28 | }, 29 | { 30 | context: context.Background(), 31 | want: nil, 32 | }, 33 | { 34 | // nolint: staticcheck // ignore using string with context value 35 | context: context.WithValue(context.Background(), key, "foo"), 36 | want: nil, 37 | }, 38 | } 39 | 40 | // run tests 41 | for _, test := range tests { 42 | got := FromContext(test.context) 43 | 44 | if !reflect.DeepEqual(got, test.want) { 45 | t.Errorf("FromContext is %v, want %v", got, test.want) 46 | } 47 | } 48 | } 49 | 50 | func TestCompiler_FromGinContext(t *testing.T) { 51 | // setup types 52 | var _engine Engine 53 | 54 | // setup tests 55 | tests := []struct { 56 | context *gin.Context 57 | value interface{} 58 | want Engine 59 | }{ 60 | { 61 | context: new(gin.Context), 62 | value: _engine, 63 | want: _engine, 64 | }, 65 | { 66 | context: new(gin.Context), 67 | value: nil, 68 | want: nil, 69 | }, 70 | { 71 | context: new(gin.Context), 72 | value: "foo", 73 | want: nil, 74 | }, 75 | } 76 | 77 | // run tests 78 | for _, test := range tests { 79 | if test.value != nil { 80 | test.context.Set(key, test.value) 81 | } 82 | 83 | got := FromGinContext(test.context) 84 | 85 | if !reflect.DeepEqual(got, test.want) { 86 | t.Errorf("FromGinContext is %v, want %v", got, test.want) 87 | } 88 | } 89 | } 90 | 91 | func TestCompiler_WithContext(t *testing.T) { 92 | // setup types 93 | var _engine Engine 94 | 95 | // nolint: staticcheck // ignore using string with context value 96 | want := context.WithValue(context.Background(), key, _engine) 97 | 98 | // run test 99 | got := WithContext(context.Background(), _engine) 100 | 101 | if !reflect.DeepEqual(got, want) { 102 | t.Errorf("WithContext is %v, want %v", got, want) 103 | } 104 | } 105 | 106 | func TestCompiler_WithGinContext(t *testing.T) { 107 | // setup types 108 | var _engine Engine 109 | 110 | want := new(gin.Context) 111 | want.Set(key, _engine) 112 | 113 | // run test 114 | got := new(gin.Context) 115 | WithGinContext(got, _engine) 116 | 117 | if !reflect.DeepEqual(got, want) { 118 | t.Errorf("WithGinContext is %v, want %v", got, want) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /compiler/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | // Package compiler provides the ability for Vela to 6 | // reconstruct a yaml configuration into an executable 7 | // pipeline. 8 | // 9 | // Usage: 10 | // 11 | // import "github.com/go-vela/compiler/compiler" 12 | package compiler 13 | -------------------------------------------------------------------------------- /compiler/engine.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package compiler 6 | 7 | import ( 8 | "github.com/go-vela/types" 9 | "github.com/go-vela/types/library" 10 | "github.com/go-vela/types/pipeline" 11 | "github.com/go-vela/types/raw" 12 | "github.com/go-vela/types/yaml" 13 | ) 14 | 15 | // Engine represents an interface for converting a yaml 16 | // configuration to an executable pipeline for Vela. 17 | type Engine interface { 18 | // Compiler Interface Functions 19 | 20 | // Compile defines a function that produces an executable 21 | // representation of a pipeline from an object. This calls 22 | // Parse internally to convert the object to a yaml configuration. 23 | Compile(interface{}) (*pipeline.Build, error) 24 | 25 | // Duplicate defines a function that 26 | // creates a clone of the Engine. 27 | Duplicate() Engine 28 | 29 | // Parse defines a function that converts 30 | // an object to a yaml configuration. 31 | Parse(interface{}) (*yaml.Build, error) 32 | 33 | // ParseRaw defines a function that converts 34 | // an object to a string. 35 | ParseRaw(interface{}) (string, error) 36 | 37 | // Validate defines a function that verifies 38 | // the yaml configuration is accurate. 39 | Validate(*yaml.Build) error 40 | 41 | // Clone Compiler Interface Functions 42 | 43 | // CloneStage defines a function that injects the 44 | // clone stage process into a yaml configuration. 45 | CloneStage(*yaml.Build) (*yaml.Build, error) 46 | // CloneStep defines a function that injects the 47 | // clone step process into a yaml configuration. 48 | CloneStep(*yaml.Build) (*yaml.Build, error) 49 | 50 | // Environment Compiler Interface Functions 51 | 52 | // EnvironmentStages defines a function that injects the environment 53 | // variables for each step in every stage into a yaml configuration. 54 | EnvironmentStages(yaml.StageSlice, raw.StringSliceMap) (yaml.StageSlice, error) 55 | // EnvironmentSteps defines a function that injects the environment 56 | // variables for each step into a yaml configuration. 57 | EnvironmentSteps(yaml.StepSlice, raw.StringSliceMap) (yaml.StepSlice, error) 58 | // EnvironmentStep defines a function that injects the environment 59 | // variables for a single step into a yaml configuration. 60 | EnvironmentStep(*yaml.Step, raw.StringSliceMap) (*yaml.Step, error) 61 | // EnvironmentServices defines a function that injects the environment 62 | // variables for each service into a yaml configuration. 63 | EnvironmentServices(yaml.ServiceSlice, raw.StringSliceMap) (yaml.ServiceSlice, error) 64 | 65 | // Expand Compiler Interface Functions 66 | 67 | // ExpandStages defines a function that injects the template 68 | // for each templated step in every stage in a yaml configuration. 69 | // nolint: lll // ignore long line length due to return args 70 | ExpandStages(*yaml.Build, map[string]*yaml.Template) (yaml.StageSlice, yaml.SecretSlice, yaml.ServiceSlice, raw.StringSliceMap, error) 71 | // ExpandSteps defines a function that injects the template 72 | // for each templated step in a yaml configuration. 73 | // nolint: lll // ignore long line length due to return args 74 | ExpandSteps(*yaml.Build, map[string]*yaml.Template) (yaml.StepSlice, yaml.SecretSlice, yaml.ServiceSlice, raw.StringSliceMap, error) 75 | 76 | // Init Compiler Interface Functions 77 | 78 | // InitStage defines a function that injects the 79 | // init stage process into a yaml configuration. 80 | InitStage(*yaml.Build) (*yaml.Build, error) 81 | // InitStep step process into a yaml configuration. 82 | InitStep(*yaml.Build) (*yaml.Build, error) 83 | 84 | // Script Compiler Interface Functions 85 | 86 | // ScriptStages defines a function that injects the script 87 | // for each step in every stage in a yaml configuration. 88 | ScriptStages(yaml.StageSlice) (yaml.StageSlice, error) 89 | // ScriptSteps defines a function that injects the script 90 | // for each step in a yaml configuration. 91 | ScriptSteps(yaml.StepSlice) (yaml.StepSlice, error) 92 | 93 | // Substitute Compiler Interface Functions 94 | 95 | // SubstituteStages defines a function that replaces every 96 | // declared environment variable with it's corresponding 97 | // value for each step in every stage in a yaml configuration. 98 | SubstituteStages(yaml.StageSlice) (yaml.StageSlice, error) 99 | // SubstituteSteps defines a function that replaces every 100 | // declared environment variable with it's corresponding 101 | // value for each step in a yaml configuration. 102 | SubstituteSteps(yaml.StepSlice) (yaml.StepSlice, error) 103 | 104 | // Transform Compiler Interface Functions 105 | 106 | // TransformStages defines a function that converts a yaml 107 | // configuration with stages into an executable pipeline. 108 | TransformStages(*pipeline.RuleData, *yaml.Build) (*pipeline.Build, error) 109 | // TransformSteps defines a function that converts a yaml 110 | // configuration with steps into an executable pipeline. 111 | TransformSteps(*pipeline.RuleData, *yaml.Build) (*pipeline.Build, error) 112 | 113 | // With Compiler Interface Functions 114 | 115 | // WithBuild defines a function that sets 116 | // the library build type in the Engine. 117 | WithBuild(*library.Build) Engine 118 | // WithComment defines a function that sets 119 | // the comment in the Engine. 120 | WithComment(string) Engine 121 | // WithFiles defines a function that sets 122 | // the changeset files in the Engine. 123 | WithFiles([]string) Engine 124 | // WithLocal defines a function that sets 125 | // the compiler local field in the Engine. 126 | WithLocal(bool) Engine 127 | // WithMetadata defines a function that sets 128 | // the compiler Metadata type in the Engine. 129 | WithMetadata(*types.Metadata) Engine 130 | // WithRepo defines a function that sets 131 | // the library repo type in the Engine. 132 | WithRepo(*library.Repo) Engine 133 | // WithUser defines a function that sets 134 | // the library user type in the Engine. 135 | WithUser(*library.User) Engine 136 | // WithUser defines a function that sets 137 | // the private github client in the Engine. 138 | WithPrivateGitHub(string, string) Engine 139 | } 140 | -------------------------------------------------------------------------------- /compiler/native/clone.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package native 6 | 7 | import ( 8 | "github.com/go-vela/types/constants" 9 | "github.com/go-vela/types/yaml" 10 | ) 11 | 12 | const ( 13 | // default image for clone process. 14 | cloneImage = "target/vela-git:v0.4.0" 15 | // default name for clone stage. 16 | cloneStageName = "clone" 17 | // default name for clone step. 18 | cloneStepName = "clone" 19 | ) 20 | 21 | // CloneStage injects the clone stage process into a yaml configuration. 22 | func (c *client) CloneStage(p *yaml.Build) (*yaml.Build, error) { 23 | // check if the compiler is setup for a local pipeline 24 | if c.local { 25 | // skip injecting the clone process 26 | return p, nil 27 | } 28 | 29 | stages := yaml.StageSlice{} 30 | 31 | // create new clone stage 32 | clone := &yaml.Stage{ 33 | Name: cloneStageName, 34 | Steps: yaml.StepSlice{ 35 | &yaml.Step{ 36 | Detach: false, 37 | Image: cloneImage, 38 | Name: cloneStepName, 39 | Privileged: false, 40 | Pull: constants.PullNotPresent, 41 | }, 42 | }, 43 | } 44 | 45 | // add clone stage as first stage 46 | stages = append(stages, clone) 47 | 48 | // add existing stages after clone stage 49 | stages = append(stages, p.Stages...) 50 | 51 | // overwrite existing stages 52 | p.Stages = stages 53 | 54 | return p, nil 55 | } 56 | 57 | // CloneStep injects the clone step process into a yaml configuration. 58 | func (c *client) CloneStep(p *yaml.Build) (*yaml.Build, error) { 59 | // check if the compiler is setup for a local pipeline 60 | if c.local { 61 | // skip injecting the clone process 62 | return p, nil 63 | } 64 | 65 | steps := yaml.StepSlice{} 66 | 67 | // create new clone step 68 | clone := &yaml.Step{ 69 | Detach: false, 70 | Image: cloneImage, 71 | Name: cloneStepName, 72 | Privileged: false, 73 | Pull: constants.PullNotPresent, 74 | } 75 | 76 | // add clone step as first step 77 | steps = append(steps, clone) 78 | 79 | // add existing steps after clone step 80 | steps = append(steps, p.Steps...) 81 | 82 | // overwrite existing steps 83 | p.Steps = steps 84 | 85 | return p, nil 86 | } 87 | -------------------------------------------------------------------------------- /compiler/native/clone_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package native 6 | 7 | import ( 8 | "flag" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/go-vela/types/yaml" 13 | "github.com/urfave/cli/v2" 14 | ) 15 | 16 | func TestNative_CloneStage(t *testing.T) { 17 | // setup types 18 | set := flag.NewFlagSet("test", 0) 19 | c := cli.NewContext(nil, set, nil) 20 | 21 | str := "foo" 22 | p := &yaml.Build{ 23 | Version: "v1", 24 | Stages: yaml.StageSlice{ 25 | &yaml.Stage{ 26 | Name: str, 27 | Steps: yaml.StepSlice{ 28 | &yaml.Step{ 29 | Image: "alpine", 30 | Name: str, 31 | Pull: "not_present", 32 | }, 33 | }, 34 | }, 35 | }, 36 | } 37 | 38 | // setup tests 39 | tests := []struct { 40 | failure bool 41 | local bool 42 | pipeline *yaml.Build 43 | want *yaml.Build 44 | }{ 45 | { 46 | failure: false, 47 | local: false, 48 | pipeline: p, 49 | want: &yaml.Build{ 50 | Version: "v1", 51 | Stages: yaml.StageSlice{ 52 | &yaml.Stage{ 53 | Name: "clone", 54 | Steps: yaml.StepSlice{ 55 | &yaml.Step{ 56 | Image: "target/vela-git:v0.4.0", 57 | Name: "clone", 58 | Pull: "not_present", 59 | }, 60 | }, 61 | }, 62 | &yaml.Stage{ 63 | Name: str, 64 | Steps: yaml.StepSlice{ 65 | &yaml.Step{ 66 | Image: "alpine", 67 | Name: str, 68 | Pull: "not_present", 69 | }, 70 | }, 71 | }, 72 | }, 73 | }, 74 | }, 75 | { 76 | failure: false, 77 | local: true, 78 | pipeline: p, 79 | want: p, 80 | }, 81 | } 82 | 83 | // run tests 84 | for _, test := range tests { 85 | compiler, err := New(c) 86 | if err != nil { 87 | t.Errorf("unable to create new compiler: %v", err) 88 | } 89 | 90 | // set the local field for the test 91 | compiler.WithLocal(test.local) 92 | 93 | got, err := compiler.CloneStage(test.pipeline) 94 | 95 | if test.failure { 96 | if err == nil { 97 | t.Errorf("CloneStage should have returned err") 98 | } 99 | 100 | continue 101 | } 102 | 103 | if err != nil { 104 | t.Errorf("CloneStage returned err: %v", err) 105 | } 106 | 107 | if !reflect.DeepEqual(got, test.want) { 108 | t.Errorf("CloneStage is %v, want %v", got, test.want) 109 | } 110 | } 111 | } 112 | 113 | func TestNative_CloneStep(t *testing.T) { 114 | // setup types 115 | set := flag.NewFlagSet("test", 0) 116 | c := cli.NewContext(nil, set, nil) 117 | 118 | str := "foo" 119 | p := &yaml.Build{ 120 | Version: "v1", 121 | Steps: yaml.StepSlice{ 122 | &yaml.Step{ 123 | Image: "alpine", 124 | Name: str, 125 | Pull: "not_present", 126 | }, 127 | }, 128 | } 129 | 130 | // setup tests 131 | tests := []struct { 132 | failure bool 133 | local bool 134 | pipeline *yaml.Build 135 | want *yaml.Build 136 | }{ 137 | { 138 | failure: false, 139 | local: false, 140 | pipeline: p, 141 | want: &yaml.Build{ 142 | Version: "v1", 143 | Steps: yaml.StepSlice{ 144 | &yaml.Step{ 145 | Image: "target/vela-git:v0.4.0", 146 | Name: "clone", 147 | Pull: "not_present", 148 | }, 149 | &yaml.Step{ 150 | Image: "alpine", 151 | Name: str, 152 | Pull: "not_present", 153 | }, 154 | }, 155 | }, 156 | }, 157 | { 158 | failure: false, 159 | local: true, 160 | pipeline: p, 161 | want: p, 162 | }, 163 | } 164 | 165 | // run tests 166 | for _, test := range tests { 167 | compiler, err := New(c) 168 | if err != nil { 169 | t.Errorf("Unable to create new compiler: %v", err) 170 | } 171 | 172 | // set the local field for the test 173 | compiler.WithLocal(test.local) 174 | 175 | got, err := compiler.CloneStep(test.pipeline) 176 | 177 | if test.failure { 178 | if err == nil { 179 | t.Errorf("CloneStep should have returned err") 180 | } 181 | 182 | continue 183 | } 184 | 185 | if err != nil { 186 | t.Errorf("CloneStep returned err: %v", err) 187 | } 188 | 189 | if !reflect.DeepEqual(got, test.want) { 190 | t.Errorf("CloneStep is %v, want %v", got, test.want) 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /compiler/native/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | // Package native provides the ability for Vela to 6 | // reconstruct a yaml configuration into an executable 7 | // pipeline. 8 | // 9 | // Usage: 10 | // 11 | // import "github.com/go-vela/compiler/compiler/native" 12 | package native 13 | -------------------------------------------------------------------------------- /compiler/native/initialize.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package native 6 | 7 | import ( 8 | "github.com/go-vela/types/constants" 9 | "github.com/go-vela/types/yaml" 10 | ) 11 | 12 | const ( 13 | // default image for init process. 14 | initImage = "#init" 15 | // default name for init stage. 16 | initStageName = "init" 17 | // default name for init step. 18 | initStepName = "init" 19 | ) 20 | 21 | // InitStage injects the init stage process into a yaml configuration. 22 | func (c *client) InitStage(p *yaml.Build) (*yaml.Build, error) { 23 | stages := yaml.StageSlice{} 24 | 25 | // create new clone stage 26 | init := &yaml.Stage{ 27 | Name: initStageName, 28 | Steps: yaml.StepSlice{ 29 | &yaml.Step{ 30 | Detach: false, 31 | Image: initImage, 32 | Name: initStepName, 33 | Privileged: false, 34 | Pull: constants.PullNotPresent, 35 | }, 36 | }, 37 | } 38 | 39 | // add init stage as first stage 40 | stages = append(stages, init) 41 | 42 | // add existing stages after init stage 43 | stages = append(stages, p.Stages...) 44 | 45 | // overwrite existing stages 46 | p.Stages = stages 47 | 48 | return p, nil 49 | } 50 | 51 | // InitStep injects the init step process into a yaml configuration. 52 | func (c *client) InitStep(p *yaml.Build) (*yaml.Build, error) { 53 | steps := yaml.StepSlice{} 54 | 55 | // create new init step 56 | init := &yaml.Step{ 57 | Detach: false, 58 | Image: initImage, 59 | Name: initStepName, 60 | Privileged: false, 61 | Pull: constants.PullNotPresent, 62 | } 63 | 64 | // add init step as first step 65 | steps = append(steps, init) 66 | 67 | // add existing steps after init step 68 | steps = append(steps, p.Steps...) 69 | 70 | // overwrite existing steps 71 | p.Steps = steps 72 | 73 | return p, nil 74 | } 75 | -------------------------------------------------------------------------------- /compiler/native/initialize_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package native 6 | 7 | import ( 8 | "flag" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/go-vela/types/yaml" 13 | "github.com/urfave/cli/v2" 14 | ) 15 | 16 | func TestNative_InitStage(t *testing.T) { 17 | // setup types 18 | set := flag.NewFlagSet("test", 0) 19 | c := cli.NewContext(nil, set, nil) 20 | 21 | str := "foo" 22 | p := &yaml.Build{ 23 | Version: "v1", 24 | Stages: yaml.StageSlice{ 25 | &yaml.Stage{ 26 | Name: str, 27 | Steps: yaml.StepSlice{ 28 | &yaml.Step{ 29 | Image: "alpine", 30 | Name: str, 31 | Pull: "not_present", 32 | }, 33 | }, 34 | }, 35 | }, 36 | } 37 | 38 | want := &yaml.Build{ 39 | Version: "v1", 40 | Stages: yaml.StageSlice{ 41 | &yaml.Stage{ 42 | Name: "init", 43 | Steps: yaml.StepSlice{ 44 | &yaml.Step{ 45 | Image: "#init", 46 | Name: "init", 47 | Pull: "not_present", 48 | }, 49 | }, 50 | }, 51 | &yaml.Stage{ 52 | Name: str, 53 | Steps: yaml.StepSlice{ 54 | &yaml.Step{ 55 | Image: "alpine", 56 | Name: str, 57 | Pull: "not_present", 58 | }, 59 | }, 60 | }, 61 | }, 62 | } 63 | 64 | // run test 65 | compiler, err := New(c) 66 | if err != nil { 67 | t.Errorf("Unable to create new compiler: %v", err) 68 | } 69 | 70 | got, err := compiler.InitStage(p) 71 | if err != nil { 72 | t.Errorf("InitStage returned err: %v", err) 73 | } 74 | 75 | if !reflect.DeepEqual(got, want) { 76 | t.Errorf("InitStage is %v, want %v", got, want) 77 | } 78 | } 79 | 80 | func TestNative_InitStep(t *testing.T) { 81 | // setup types 82 | set := flag.NewFlagSet("test", 0) 83 | c := cli.NewContext(nil, set, nil) 84 | 85 | str := "foo" 86 | p := &yaml.Build{ 87 | Version: "v1", 88 | Steps: yaml.StepSlice{ 89 | &yaml.Step{ 90 | Image: "alpine", 91 | Name: str, 92 | Pull: "not_present", 93 | }, 94 | }, 95 | } 96 | 97 | want := &yaml.Build{ 98 | Version: "v1", 99 | Steps: yaml.StepSlice{ 100 | &yaml.Step{ 101 | Image: "#init", 102 | Name: "init", 103 | Pull: "not_present", 104 | }, 105 | &yaml.Step{ 106 | Image: "alpine", 107 | Name: str, 108 | Pull: "not_present", 109 | }, 110 | }, 111 | } 112 | // run test 113 | compiler, err := New(c) 114 | if err != nil { 115 | t.Errorf("Unable to create new compiler: %v", err) 116 | } 117 | 118 | got, err := compiler.InitStep(p) 119 | if err != nil { 120 | t.Errorf("InitStep returned err: %v", err) 121 | } 122 | 123 | if !reflect.DeepEqual(got, want) { 124 | t.Errorf("InitStep is %v, want %v", got, want) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /compiler/native/native.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package native 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/go-vela/compiler/compiler" 11 | 12 | "github.com/go-vela/compiler/registry" 13 | "github.com/go-vela/compiler/registry/github" 14 | 15 | "github.com/go-vela/types" 16 | "github.com/go-vela/types/library" 17 | 18 | "github.com/sirupsen/logrus" 19 | "github.com/urfave/cli/v2" 20 | ) 21 | 22 | type ModificationConfig struct { 23 | Timeout time.Duration 24 | Retries int 25 | Endpoint string 26 | Secret string 27 | } 28 | 29 | type client struct { 30 | Github registry.Service 31 | PrivateGithub registry.Service 32 | UsePrivateGithub bool 33 | ModificationService ModificationConfig 34 | 35 | build *library.Build 36 | comment string 37 | files []string 38 | local bool 39 | metadata *types.Metadata 40 | repo *library.Repo 41 | user *library.User 42 | } 43 | 44 | // New returns a Pipeline implementation that integrates with the supported registries. 45 | // 46 | // nolint: revive // ignore returning unexported client 47 | func New(ctx *cli.Context) (*client, error) { 48 | logrus.Debug("Creating registry clients from CLI configuration") 49 | c := new(client) 50 | 51 | if ctx.String("modification-addr") != "" { 52 | c.ModificationService = ModificationConfig{ 53 | Timeout: ctx.Duration("modification-timeout"), 54 | Endpoint: ctx.String("modification-addr"), 55 | Secret: ctx.String("modification-secret"), 56 | Retries: ctx.Int("modification-retries"), 57 | } 58 | } 59 | 60 | // setup github template service 61 | github, err := setupGithub() 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | c.Github = github 67 | 68 | if ctx.Bool("github-driver") { 69 | logrus.Tracef("setting up Private GitHub Client for %s", ctx.String("github-url")) 70 | // setup private github service 71 | privGithub, err := setupPrivateGithub(ctx.String("github-url"), ctx.String("github-token")) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | c.PrivateGithub = privGithub 77 | c.UsePrivateGithub = true 78 | } 79 | 80 | return c, nil 81 | } 82 | 83 | // setupGithub is a helper function to setup the 84 | // Github registry service from the CLI arguments. 85 | func setupGithub() (registry.Service, error) { 86 | logrus.Tracef("Creating %s registry client from CLI configuration", "github") 87 | return github.New("", "") 88 | } 89 | 90 | // setupPrivateGithub is a helper function to setup the 91 | // Github registry service from the CLI arguments. 92 | func setupPrivateGithub(addr, token string) (registry.Service, error) { 93 | logrus.Tracef("Creating private %s registry client from CLI configuration", "github") 94 | return github.New(addr, token) 95 | } 96 | 97 | // Duplicate creates a clone of the Engine. 98 | func (c *client) Duplicate() compiler.Engine { 99 | cc := new(client) 100 | 101 | // copy the essential fields from the existing client 102 | cc.Github = c.Github 103 | cc.PrivateGithub = c.PrivateGithub 104 | cc.UsePrivateGithub = c.UsePrivateGithub 105 | cc.ModificationService = c.ModificationService 106 | 107 | return cc 108 | } 109 | 110 | // WithBuild sets the library build type in the Engine. 111 | func (c *client) WithBuild(b *library.Build) compiler.Engine { 112 | if b != nil { 113 | c.build = b 114 | } 115 | 116 | return c 117 | } 118 | 119 | // WithComment sets the comment in the Engine. 120 | func (c *client) WithComment(cmt string) compiler.Engine { 121 | if cmt != "" { 122 | c.comment = cmt 123 | } 124 | 125 | return c 126 | } 127 | 128 | // WithFiles sets the changeset files in the Engine. 129 | func (c *client) WithFiles(f []string) compiler.Engine { 130 | if f != nil { 131 | c.files = f 132 | } 133 | 134 | return c 135 | } 136 | 137 | // WithLocal sets the compiler metadata type in the Engine. 138 | func (c *client) WithLocal(local bool) compiler.Engine { 139 | c.local = local 140 | 141 | return c 142 | } 143 | 144 | // WithMetadata sets the compiler metadata type in the Engine. 145 | func (c *client) WithMetadata(m *types.Metadata) compiler.Engine { 146 | if m != nil { 147 | c.metadata = m 148 | } 149 | 150 | return c 151 | } 152 | 153 | // WithPrivateGitHub sets the private github client in the Engine. 154 | func (c *client) WithPrivateGitHub(url, token string) compiler.Engine { 155 | if len(url) != 0 && len(token) != 0 { 156 | privGithub, _ := setupPrivateGithub(url, token) 157 | 158 | c.PrivateGithub = privGithub 159 | } 160 | 161 | return c 162 | } 163 | 164 | // WithRepo sets the library repo type in the Engine. 165 | func (c *client) WithRepo(r *library.Repo) compiler.Engine { 166 | if r != nil { 167 | c.repo = r 168 | } 169 | 170 | return c 171 | } 172 | 173 | // WithUser sets the library user type in the Engine. 174 | func (c *client) WithUser(u *library.User) compiler.Engine { 175 | if u != nil { 176 | c.user = u 177 | } 178 | 179 | return c 180 | } 181 | -------------------------------------------------------------------------------- /compiler/native/parse.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package native 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | 13 | "github.com/go-vela/compiler/template/native" 14 | "github.com/go-vela/compiler/template/starlark" 15 | "github.com/go-vela/types/constants" 16 | types "github.com/go-vela/types/yaml" 17 | 18 | "github.com/buildkite/yaml" 19 | ) 20 | 21 | // ParseRaw converts an object to a string. 22 | func (c *client) ParseRaw(v interface{}) (string, error) { 23 | switch v := v.(type) { 24 | case []byte: 25 | return string(v), nil 26 | case *os.File: 27 | return ParseFileRaw(v) 28 | case io.Reader: 29 | return ParseReaderRaw(v) 30 | case string: 31 | // check if string is path to file 32 | _, err := os.Stat(v) 33 | if err == nil { 34 | // parse string as path to yaml configuration 35 | return ParsePathRaw(v) 36 | } 37 | 38 | // parse string as yaml configuration 39 | return v, nil 40 | default: 41 | return "", fmt.Errorf("unable to parse yaml: unrecognized type %T", v) 42 | } 43 | } 44 | 45 | // Parse converts an object to a yaml configuration. 46 | func (c *client) Parse(v interface{}) (*types.Build, error) { 47 | var p *types.Build 48 | 49 | switch c.repo.GetPipelineType() { 50 | case constants.PipelineTypeGo: 51 | // expand the base configuration 52 | parsedRaw, err := c.ParseRaw(v) 53 | if err != nil { 54 | return nil, err 55 | } 56 | p, err = native.RenderBuild(parsedRaw, c.EnvironmentBuild()) 57 | if err != nil { 58 | return nil, err 59 | } 60 | case constants.PipelineTypeStarlark: 61 | // expand the base configuration 62 | parsedRaw, err := c.ParseRaw(v) 63 | if err != nil { 64 | return nil, err 65 | } 66 | p, err = starlark.RenderBuild(parsedRaw, c.EnvironmentBuild()) 67 | if err != nil { 68 | return nil, err 69 | } 70 | case constants.PipelineTypeYAML, "": 71 | switch v := v.(type) { 72 | case []byte: 73 | return ParseBytes(v) 74 | case *os.File: 75 | return ParseFile(v) 76 | case io.Reader: 77 | return ParseReader(v) 78 | case string: 79 | // check if string is path to file 80 | _, err := os.Stat(v) 81 | if err == nil { 82 | // parse string as path to yaml configuration 83 | return ParsePath(v) 84 | } 85 | 86 | // parse string as yaml configuration 87 | return ParseString(v) 88 | default: 89 | return nil, fmt.Errorf("unable to parse yaml: unrecognized type %T", v) 90 | } 91 | default: 92 | // nolint:lll // detailed error message 93 | return nil, fmt.Errorf("unable to parse config: unrecognized pipeline_type of %s", c.repo.GetPipelineType()) 94 | } 95 | 96 | return p, nil 97 | } 98 | 99 | // ParseBytes converts a byte slice to a yaml configuration. 100 | func ParseBytes(b []byte) (*types.Build, error) { 101 | config := new(types.Build) 102 | 103 | // unmarshal the bytes into the yaml configuration 104 | err := yaml.Unmarshal(b, config) 105 | if err != nil { 106 | return nil, fmt.Errorf("unable to unmarshal yaml: %v", err) 107 | } 108 | 109 | return config, nil 110 | } 111 | 112 | // ParseFile converts an os.File into a yaml configuration. 113 | func ParseFile(f *os.File) (*types.Build, error) { 114 | return ParseReader(f) 115 | } 116 | 117 | // ParseFileRaw converts an os.File into a string. 118 | func ParseFileRaw(f *os.File) (string, error) { 119 | return ParseReaderRaw(f) 120 | } 121 | 122 | // ParsePath converts a file path into a yaml configuration. 123 | func ParsePath(p string) (*types.Build, error) { 124 | // open the file for reading 125 | f, err := os.Open(p) 126 | if err != nil { 127 | return nil, fmt.Errorf("unable to open yaml file %s: %v", p, err) 128 | } 129 | 130 | defer f.Close() 131 | 132 | return ParseReader(f) 133 | } 134 | 135 | // ParsePathRaw converts a file path into a yaml configuration. 136 | func ParsePathRaw(p string) (string, error) { 137 | // open the file for reading 138 | f, err := os.Open(p) 139 | if err != nil { 140 | return "", fmt.Errorf("unable to open yaml file %s: %v", p, err) 141 | } 142 | 143 | defer f.Close() 144 | 145 | return ParseReaderRaw(f) 146 | } 147 | 148 | // ParseReader converts an io.Reader into a yaml configuration. 149 | func ParseReader(r io.Reader) (*types.Build, error) { 150 | // read all the bytes from the reader 151 | b, err := ioutil.ReadAll(r) 152 | if err != nil { 153 | return nil, fmt.Errorf("unable to read bytes for yaml: %v", err) 154 | } 155 | 156 | return ParseBytes(b) 157 | } 158 | 159 | // ParseReaderRaw converts an io.Reader into a yaml configuration. 160 | func ParseReaderRaw(r io.Reader) (string, error) { 161 | // read all the bytes from the reader 162 | b, err := ioutil.ReadAll(r) 163 | if err != nil { 164 | return "", fmt.Errorf("unable to read bytes for yaml: %v", err) 165 | } 166 | 167 | return string(b), nil 168 | } 169 | 170 | // ParseString converts a string into a yaml configuration. 171 | func ParseString(s string) (*types.Build, error) { 172 | return ParseBytes([]byte(s)) 173 | } 174 | -------------------------------------------------------------------------------- /compiler/native/script.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package native 6 | 7 | import ( 8 | "bytes" 9 | "encoding/base64" 10 | "fmt" 11 | "strings" 12 | 13 | "github.com/go-vela/types/yaml" 14 | ) 15 | 16 | // ScriptStages injects the script for each step in every stage in a yaml configuration. 17 | func (c *client) ScriptStages(s yaml.StageSlice) (yaml.StageSlice, error) { 18 | // iterate through all stages 19 | for _, stage := range s { 20 | // inject the scripts into the steps for the stage 21 | steps, err := c.ScriptSteps(stage.Steps) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | stage.Steps = steps 27 | } 28 | 29 | return s, nil 30 | } 31 | 32 | // ScriptSteps injects the script for each step in a yaml configuration. 33 | func (c *client) ScriptSteps(s yaml.StepSlice) (yaml.StepSlice, error) { 34 | // iterate through all steps 35 | for _, step := range s { 36 | // skip if no commands block for the step 37 | if len(step.Commands) == 0 { 38 | continue 39 | } 40 | 41 | // set the default home 42 | // nolint: goconst // ignore making this a constant for now 43 | home := "/root" 44 | // override the home value if user is defined 45 | // TODO: 46 | // - add ability to override user home directory 47 | if step.User != "" { 48 | home = fmt.Sprintf("/home/%s", step.User) 49 | } 50 | 51 | // generate script from commands 52 | script := generateScriptPosix(step.Commands) 53 | 54 | // set the entrypoint for the step 55 | step.Entrypoint = []string{"/bin/sh", "-c"} 56 | 57 | // set the commands for the step 58 | step.Commands = []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"} 59 | 60 | // set the environment variables for the step 61 | step.Environment["VELA_BUILD_SCRIPT"] = script 62 | step.Environment["HOME"] = home 63 | // nolint: goconst // ignore making this a constant for now 64 | step.Environment["SHELL"] = "/bin/sh" 65 | } 66 | 67 | return s, nil 68 | } 69 | 70 | // generateScriptPosix is a helper function that generates a build script 71 | // for a linux container using the given commands. 72 | func generateScriptPosix(commands []string) string { 73 | var buf bytes.Buffer 74 | 75 | // iterate through each command provided 76 | for _, command := range commands { 77 | // safely escape entire command 78 | escaped := fmt.Sprintf("%q", command) 79 | 80 | // safely escape trace character 81 | escaped = strings.Replace(escaped, "$", `\$`, -1) 82 | 83 | // write escaped lines to buffer 84 | buf.WriteString(fmt.Sprintf( 85 | traceScript, 86 | escaped, 87 | command, 88 | )) 89 | } 90 | 91 | // create build script with netrc and buffer information 92 | script := fmt.Sprintf( 93 | setupScript, 94 | buf.String(), 95 | ) 96 | 97 | return base64.StdEncoding.EncodeToString([]byte(script)) 98 | } 99 | 100 | // setupScript is a helper script this is added to the build to ensure 101 | // a minimum set of environment variables are set correctly. 102 | const setupScript = ` 103 | cat < $HOME/.netrc 104 | machine $VELA_NETRC_MACHINE 105 | login $VELA_NETRC_USERNAME 106 | password $VELA_NETRC_PASSWORD 107 | EOF 108 | chmod 0600 $HOME/.netrc 109 | unset VELA_NETRC_MACHINE 110 | unset VELA_NETRC_USERNAME 111 | unset VELA_NETRC_PASSWORD 112 | unset VELA_BUILD_SCRIPT 113 | %s 114 | ` 115 | 116 | // traceScript is a helper script that is added to the build script 117 | // to trace a command. 118 | const traceScript = ` 119 | echo $ %s 120 | %s 121 | ` 122 | -------------------------------------------------------------------------------- /compiler/native/script_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package native 6 | 7 | import ( 8 | "flag" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/go-vela/types/yaml" 13 | 14 | "github.com/urfave/cli/v2" 15 | ) 16 | 17 | func TestNative_ScriptStages(t *testing.T) { 18 | // setup types 19 | set := flag.NewFlagSet("test", 0) 20 | c := cli.NewContext(nil, set, nil) 21 | 22 | baseEnv := environment(nil, nil, nil, nil) 23 | 24 | s := yaml.StageSlice{ 25 | &yaml.Stage{ 26 | Name: "install", 27 | Steps: yaml.StepSlice{ 28 | &yaml.Step{ 29 | Commands: []string{"./gradlew downloadDependencies"}, 30 | Environment: baseEnv, 31 | Image: "openjdk:latest", 32 | Name: "install", 33 | Pull: "always", 34 | }, 35 | }, 36 | }, 37 | &yaml.Stage{ 38 | Name: "test", 39 | Needs: []string{"install"}, 40 | Steps: yaml.StepSlice{ 41 | &yaml.Step{ 42 | Commands: []string{"./gradlew check"}, 43 | Environment: baseEnv, 44 | Image: "openjdk:latest", 45 | Name: "test", 46 | Pull: "always", 47 | }, 48 | }, 49 | }, 50 | } 51 | 52 | baseEnv["HOME"] = "/root" 53 | baseEnv["SHELL"] = "/bin/sh" 54 | 55 | installEnv := baseEnv 56 | installEnv["VELA_BUILD_SCRIPT"] = generateScriptPosix([]string{"./gradlew downloadDependencies"}) 57 | testEnv := baseEnv 58 | testEnv["VELA_BUILD_SCRIPT"] = generateScriptPosix([]string{"./gradlew check"}) 59 | 60 | want := yaml.StageSlice{ 61 | &yaml.Stage{ 62 | Name: "install", 63 | Steps: yaml.StepSlice{ 64 | &yaml.Step{ 65 | Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, 66 | Entrypoint: []string{"/bin/sh", "-c"}, 67 | Environment: installEnv, 68 | Image: "openjdk:latest", 69 | Name: "install", 70 | Pull: "always", 71 | }, 72 | }, 73 | }, 74 | &yaml.Stage{ 75 | Name: "test", 76 | Needs: []string{"install"}, 77 | Steps: yaml.StepSlice{ 78 | &yaml.Step{ 79 | Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, 80 | Entrypoint: []string{"/bin/sh", "-c"}, 81 | Environment: testEnv, 82 | Image: "openjdk:latest", 83 | Name: "test", 84 | Pull: "always", 85 | }, 86 | }, 87 | }, 88 | } 89 | 90 | // run test 91 | compiler, err := New(c) 92 | if err != nil { 93 | t.Errorf("Creating compiler returned err: %v", err) 94 | } 95 | 96 | got, err := compiler.ScriptStages(s) 97 | if err != nil { 98 | t.Errorf("ScriptStages returned err: %v", err) 99 | } 100 | 101 | if !reflect.DeepEqual(got, want) { 102 | t.Errorf("ScriptStages is %v, want %v", got, want) 103 | } 104 | } 105 | 106 | func TestNative_ScriptSteps(t *testing.T) { 107 | // setup types 108 | set := flag.NewFlagSet("test", 0) 109 | c := cli.NewContext(nil, set, nil) 110 | 111 | baseEnv := environment(nil, nil, nil, nil) 112 | baseEnv["HOME"] = "/root" 113 | baseEnv["SHELL"] = "/bin/sh" 114 | 115 | installEnv := baseEnv 116 | installEnv["VELA_BUILD_SCRIPT"] = generateScriptPosix([]string{"./gradlew downloadDependencies"}) 117 | testEnv := baseEnv 118 | testEnv["VELA_BUILD_SCRIPT"] = generateScriptPosix([]string{"./gradlew check"}) 119 | 120 | type args struct { 121 | s yaml.StepSlice 122 | } 123 | tests := []struct { 124 | name string 125 | args args 126 | want yaml.StepSlice 127 | wantErr bool 128 | }{ 129 | {"no user defined", args{s: yaml.StepSlice{ 130 | &yaml.Step{ 131 | Commands: []string{"./gradlew downloadDependencies"}, 132 | Environment: baseEnv, 133 | Image: "openjdk:latest", 134 | Name: "install", 135 | Pull: "always", 136 | }, 137 | &yaml.Step{ 138 | Commands: []string{"./gradlew check"}, 139 | Environment: baseEnv, 140 | Image: "openjdk:latest", 141 | Name: "test", 142 | Pull: "always", 143 | }, 144 | }}, yaml.StepSlice{ 145 | &yaml.Step{ 146 | Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, 147 | Entrypoint: []string{"/bin/sh", "-c"}, 148 | Environment: installEnv, 149 | Image: "openjdk:latest", 150 | Name: "install", 151 | Pull: "always", 152 | }, 153 | &yaml.Step{ 154 | Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, 155 | Entrypoint: []string{"/bin/sh", "-c"}, 156 | Environment: testEnv, 157 | Image: "openjdk:latest", 158 | Name: "test", 159 | Pull: "always", 160 | }, 161 | }, false}, 162 | {"root user defined", args{s: yaml.StepSlice{ 163 | &yaml.Step{ 164 | Commands: []string{"./gradlew downloadDependencies"}, 165 | Environment: baseEnv, 166 | Image: "openjdk:latest", 167 | Name: "install", 168 | User: "root", 169 | Pull: "always", 170 | }, 171 | &yaml.Step{ 172 | Commands: []string{"./gradlew check"}, 173 | Environment: baseEnv, 174 | Image: "openjdk:latest", 175 | Name: "test", 176 | User: "root", 177 | Pull: "always", 178 | }, 179 | }}, yaml.StepSlice{ 180 | &yaml.Step{ 181 | Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, 182 | Entrypoint: []string{"/bin/sh", "-c"}, 183 | Environment: installEnv, 184 | Image: "openjdk:latest", 185 | Name: "install", 186 | User: "root", 187 | Pull: "always", 188 | }, 189 | &yaml.Step{ 190 | Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, 191 | Entrypoint: []string{"/bin/sh", "-c"}, 192 | Environment: testEnv, 193 | Image: "openjdk:latest", 194 | Name: "test", 195 | User: "root", 196 | Pull: "always", 197 | }, 198 | }, false}, 199 | {"foo user defined", args{s: yaml.StepSlice{ 200 | &yaml.Step{ 201 | Commands: []string{"./gradlew downloadDependencies"}, 202 | Environment: baseEnv, 203 | Image: "openjdk:latest", 204 | Name: "install", 205 | User: "foo", 206 | Pull: "always", 207 | }, 208 | &yaml.Step{ 209 | Commands: []string{"./gradlew check"}, 210 | Environment: baseEnv, 211 | Image: "openjdk:latest", 212 | Name: "test", 213 | User: "foo", 214 | Pull: "always", 215 | }, 216 | }}, yaml.StepSlice{ 217 | &yaml.Step{ 218 | Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, 219 | Entrypoint: []string{"/bin/sh", "-c"}, 220 | Environment: installEnv, 221 | Image: "openjdk:latest", 222 | Name: "install", 223 | User: "foo", 224 | Pull: "always", 225 | }, 226 | &yaml.Step{ 227 | Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, 228 | Entrypoint: []string{"/bin/sh", "-c"}, 229 | Environment: testEnv, 230 | Image: "openjdk:latest", 231 | Name: "test", 232 | User: "foo", 233 | Pull: "always", 234 | }, 235 | }, false}, 236 | } 237 | for _, tt := range tests { 238 | t.Run(tt.name, func(t *testing.T) { 239 | compiler, err := New(c) 240 | if err != nil { 241 | t.Errorf("Creating compiler returned err: %v", err) 242 | } 243 | got, err := compiler.ScriptSteps(tt.args.s) 244 | if (err != nil) != tt.wantErr { 245 | t.Errorf("ScriptSteps() error = %v, wantErr %v", err, tt.wantErr) 246 | return 247 | } 248 | if !reflect.DeepEqual(got, tt.want) { 249 | t.Errorf("ScriptSteps() got = %v, want %v", got, tt.want) 250 | } 251 | }) 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /compiler/native/substitute.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package native 6 | 7 | import ( 8 | "fmt" 9 | "strings" 10 | 11 | "github.com/buildkite/yaml" 12 | 13 | "github.com/drone/envsubst" 14 | 15 | types "github.com/go-vela/types/yaml" 16 | ) 17 | 18 | // SubstituteStages replaces every declared environment 19 | // variable with it's corresponding value for each step 20 | // in every stage in a yaml configuration. 21 | func (c *client) SubstituteStages(s types.StageSlice) (types.StageSlice, error) { 22 | // iterate through all stages 23 | for _, stage := range s { 24 | // inject the scripts into the steps for the stage 25 | steps, err := c.SubstituteSteps(stage.Steps) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | stage.Steps = steps 31 | } 32 | 33 | return s, nil 34 | } 35 | 36 | // SubstituteSteps replaces every declared environment 37 | // variable with it's corresponding value for each step 38 | // in a yaml configuration. 39 | func (c *client) SubstituteSteps(s types.StepSlice) (types.StepSlice, error) { 40 | // iterate through all steps 41 | for _, step := range s { 42 | // marshal step configuration 43 | body, err := yaml.Marshal(step) 44 | if err != nil { 45 | return nil, fmt.Errorf("unable to marshal configuration: %v", err) 46 | } 47 | 48 | // create substitute function 49 | subFunc := func(name string) string { 50 | // check for the environment variable 51 | env, ok := step.Environment[name] 52 | if !ok { 53 | // return the original declaration if 54 | // the environment variable isn't found 55 | return fmt.Sprintf("${%s}", name) 56 | } 57 | 58 | // check for a new line 59 | if strings.Contains(env, "\n") { 60 | // escape the environment variable 61 | env = fmt.Sprintf("%q", env) 62 | } 63 | 64 | return env 65 | } 66 | 67 | // substitute the environment variables 68 | subStep, err := envsubst.Eval(string(body), subFunc) 69 | if err != nil { 70 | return nil, fmt.Errorf("unable to substitute environment variables: %v", err) 71 | } 72 | 73 | // unmarshal step configuration 74 | err = yaml.Unmarshal([]byte(subStep), step) 75 | if err != nil { 76 | return nil, fmt.Errorf("unable to unmarshal configuration: %v", err) 77 | } 78 | } 79 | 80 | return s, nil 81 | } 82 | -------------------------------------------------------------------------------- /compiler/native/substitute_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package native 6 | 7 | import ( 8 | "flag" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/go-vela/types/yaml" 13 | 14 | "github.com/urfave/cli/v2" 15 | ) 16 | 17 | func TestNative_SubstituteStages(t *testing.T) { 18 | // setup types 19 | set := flag.NewFlagSet("test", 0) 20 | c := cli.NewContext(nil, set, nil) 21 | 22 | s := yaml.StageSlice{ 23 | { 24 | Name: "simple", 25 | Steps: yaml.StepSlice{ 26 | { 27 | Commands: []string{"echo ${FOO}", "echo $${BAR}"}, 28 | Environment: map[string]string{"FOO": "baz", "BAR": "baz"}, 29 | Image: "alpine:latest", 30 | Name: "simple", 31 | Pull: "always", 32 | }, 33 | }, 34 | }, 35 | { 36 | Name: "advanced", 37 | Steps: yaml.StepSlice{ 38 | { 39 | Commands: []string{"echo ${COMPLEX}"}, 40 | Environment: map[string]string{"COMPLEX": "{\"hello\":\n \"world\"}"}, 41 | Image: "alpine:latest", 42 | Name: "advanced", 43 | Pull: "always", 44 | }, 45 | }, 46 | }, 47 | { 48 | Name: "not_found", 49 | Steps: yaml.StepSlice{ 50 | { 51 | Commands: []string{"echo $NOT_FOUND", "echo ${NOT_FOUND}", "echo $${NOT_FOUND}"}, 52 | Environment: map[string]string{"FOO": "baz", "BAR": "baz"}, 53 | Image: "alpine:latest", 54 | Name: "not_found", 55 | Pull: "always", 56 | }, 57 | }, 58 | }, 59 | } 60 | 61 | want := yaml.StageSlice{ 62 | { 63 | Name: "simple", 64 | Steps: yaml.StepSlice{ 65 | { 66 | Commands: []string{"echo baz", "echo ${BAR}"}, 67 | Environment: map[string]string{"FOO": "baz", "BAR": "baz"}, 68 | Image: "alpine:latest", 69 | Name: "simple", 70 | Pull: "always", 71 | }, 72 | }, 73 | }, 74 | { 75 | Name: "advanced", 76 | Steps: yaml.StepSlice{ 77 | { 78 | Commands: []string{"echo \"{\\\"hello\\\":\\n \\\"world\\\"}\""}, 79 | Environment: map[string]string{"COMPLEX": "{\"hello\":\n \"world\"}"}, 80 | Image: "alpine:latest", 81 | Name: "advanced", 82 | Pull: "always", 83 | }, 84 | }, 85 | }, 86 | { 87 | Name: "not_found", 88 | Steps: yaml.StepSlice{ 89 | { 90 | Commands: []string{"echo $NOT_FOUND", "echo ${NOT_FOUND}", "echo ${NOT_FOUND}"}, 91 | Environment: map[string]string{"FOO": "baz", "BAR": "baz"}, 92 | Image: "alpine:latest", 93 | Name: "not_found", 94 | Pull: "always", 95 | }, 96 | }, 97 | }, 98 | } 99 | 100 | // run test 101 | compiler, err := New(c) 102 | if err != nil { 103 | t.Errorf("Creating compiler returned err: %v", err) 104 | } 105 | 106 | got, err := compiler.SubstituteStages(s) 107 | if err != nil { 108 | t.Errorf("SubstituteStages returned err: %v", err) 109 | } 110 | 111 | if !reflect.DeepEqual(got, want) { 112 | t.Errorf("SubstituteStages is %v, want %v", got, want) 113 | } 114 | } 115 | 116 | func TestNative_SubstituteSteps(t *testing.T) { 117 | // setup types 118 | set := flag.NewFlagSet("test", 0) 119 | c := cli.NewContext(nil, set, nil) 120 | 121 | p := yaml.StepSlice{ 122 | { 123 | Commands: []string{"echo ${FOO}", "echo $${BAR}"}, 124 | Environment: map[string]string{"FOO": "baz", "BAR": "baz"}, 125 | Image: "alpine:latest", 126 | Name: "simple", 127 | Pull: "always", 128 | }, 129 | { 130 | Commands: []string{"echo ${COMPLEX}"}, 131 | Environment: map[string]string{"COMPLEX": "{\"hello\":\n \"world\"}"}, 132 | Image: "alpine:latest", 133 | Name: "advanced", 134 | Pull: "always", 135 | }, 136 | { 137 | Commands: []string{"echo $NOT_FOUND", "echo ${NOT_FOUND}", "echo $${NOT_FOUND}"}, 138 | Environment: map[string]string{"FOO": "baz", "BAR": "baz"}, 139 | Image: "alpine:latest", 140 | Name: "not_found", 141 | Pull: "always", 142 | }, 143 | } 144 | 145 | want := yaml.StepSlice{ 146 | { 147 | Commands: []string{"echo baz", "echo ${BAR}"}, 148 | Environment: map[string]string{"FOO": "baz", "BAR": "baz"}, 149 | Image: "alpine:latest", 150 | Name: "simple", 151 | Pull: "always", 152 | }, 153 | { 154 | Commands: []string{"echo \"{\\\"hello\\\":\\n \\\"world\\\"}\""}, 155 | Environment: map[string]string{"COMPLEX": "{\"hello\":\n \"world\"}"}, 156 | Image: "alpine:latest", 157 | Name: "advanced", 158 | Pull: "always", 159 | }, 160 | { 161 | Commands: []string{"echo $NOT_FOUND", "echo ${NOT_FOUND}", "echo ${NOT_FOUND}"}, 162 | Environment: map[string]string{"FOO": "baz", "BAR": "baz"}, 163 | Image: "alpine:latest", 164 | Name: "not_found", 165 | Pull: "always", 166 | }, 167 | } 168 | 169 | // run test 170 | compiler, err := New(c) 171 | if err != nil { 172 | t.Errorf("Creating compiler returned err: %v", err) 173 | } 174 | 175 | got, err := compiler.SubstituteSteps(p) 176 | if err != nil { 177 | t.Errorf("SubstituteSteps returned err: %v", err) 178 | } 179 | 180 | if !reflect.DeepEqual(got, want) { 181 | t.Errorf("SubstituteSteps is %v, want %v", got, want) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /compiler/native/testdata/clone_false.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "1" 3 | metadata: 4 | clone: false 5 | 6 | steps: 7 | - name: foo 8 | parameters: 9 | registry: foo 10 | image: alpine 11 | pull: true 12 | -------------------------------------------------------------------------------- /compiler/native/testdata/clone_replace.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "1" 3 | metadata: 4 | clone: false 5 | 6 | steps: 7 | - name: clone 8 | image: target/vela-git:v0.4.0 9 | parameters: 10 | depth: 5 11 | pull: always 12 | 13 | - name: foo 14 | parameters: 15 | registry: foo 16 | image: alpine 17 | pull: true 18 | -------------------------------------------------------------------------------- /compiler/native/testdata/clone_true.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "1" 3 | metadata: 4 | clone: true 5 | 6 | steps: 7 | - name: foo 8 | parameters: 9 | registry: foo 10 | image: alpine 11 | pull: true 12 | -------------------------------------------------------------------------------- /compiler/native/testdata/invalid.yml: -------------------------------------------------------------------------------- 1 | --- 2 | !@#$%^&*() -------------------------------------------------------------------------------- /compiler/native/testdata/invalid_type.yml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | 3 | metadata: 4 | os: linux 5 | 6 | templates: 7 | - name: gradle 8 | source: github.example.com/foo/bar/template.yml 9 | type: github 10 | - name: gradle 11 | source: github.example.com/foo/bar/template.yml 12 | type: bla 13 | 14 | steps: 15 | # would execute the following steps: 16 | # 1. sample_get_dependencies 17 | # 2. sample_test 18 | # 3. sample_build 19 | - name: sample 20 | template: 21 | name: gradle 22 | vars: 23 | image: openjdk:latest 24 | environment: "{ GRADLE_USER_HOME: .gradle, GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false }" 25 | pull_policy: "pull: true" 26 | 27 | - name: docker 28 | image: plugins/docker:18.09 29 | parameters: 30 | registry: index.docker.io 31 | repo: github/octocat 32 | tags: 33 | - latest 34 | - dev 35 | pull: true 36 | secrets: 37 | - source: docker_username 38 | target: registry_username 39 | - source: docker_password 40 | target: registry_password 41 | 42 | secrets: 43 | # Repo secrets 44 | - name: docker_username 45 | key: org/repo/docker/username 46 | engine: native 47 | type: repo 48 | 49 | - name: docker_password 50 | key: org/repo/docker/password 51 | engine: vault 52 | type: repo 53 | -------------------------------------------------------------------------------- /compiler/native/testdata/metadata.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "1" 3 | 4 | metadata: 5 | template: false 6 | -------------------------------------------------------------------------------- /compiler/native/testdata/parameters.yml: -------------------------------------------------------------------------------- 1 | --- 2 | steps: 3 | - name: docker 4 | image: plugins/docker:18.09 5 | parameters: 6 | registry: index.docker.io 7 | repo: github/octocat 8 | tags: 9 | - latest 10 | - dev 11 | pull: true 12 | secrets: [ docker_username, docker_password ] 13 | -------------------------------------------------------------------------------- /compiler/native/testdata/pipeline_type.star: -------------------------------------------------------------------------------- 1 | def main(ctx): 2 | image = "alpine" 3 | 4 | return { 5 | 'version': '1', 6 | 'steps': [ 7 | { 8 | "name": "foo", 9 | "image": image, 10 | "parameters": { 11 | "registry": "foo" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /compiler/native/testdata/pipeline_type_default.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "1" 3 | 4 | steps: 5 | - name: foo 6 | parameters: 7 | registry: foo 8 | image: alpine 9 | -------------------------------------------------------------------------------- /compiler/native/testdata/pipeline_type_go.yml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | 3 | {{$image := "alpine"}} 4 | 5 | steps: 6 | - name: foo 7 | parameters: 8 | registry: foo 9 | image: {{ $image }} 10 | -------------------------------------------------------------------------------- /compiler/native/testdata/secrets.yml: -------------------------------------------------------------------------------- 1 | --- 2 | secrets: 3 | # Repo secrets 4 | - name: docker_username 5 | key: org/repo/docker/username 6 | engine: native 7 | type: repo 8 | 9 | - name: docker_password 10 | key: org/repo/docker/password 11 | engine: vault 12 | type: repo 13 | 14 | # Org secrets 15 | - name: docker_username 16 | key: org/docker/username 17 | engine: native 18 | type: org 19 | 20 | - name: docker_password 21 | key: org/docker/password 22 | engine: vault 23 | type: org 24 | 25 | # Shared secrets 26 | - name: docker_username 27 | key: org/team/docker/username 28 | engine: native 29 | type: shared 30 | 31 | - name: docker_password 32 | key: org/team/docker/password 33 | engine: vault 34 | type: shared 35 | -------------------------------------------------------------------------------- /compiler/native/testdata/stages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | stages: 3 | 4 | install: 5 | steps: 6 | - name: install 7 | commands: 8 | - ./gradlew downloadDependencies 9 | environment: 10 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 11 | GRADLE_USER_HOME: .gradle 12 | image: openjdk:latest 13 | pull: true 14 | 15 | test: 16 | needs: [ install ] 17 | steps: 18 | - name: test 19 | commands: 20 | - ./gradlew check 21 | environment: 22 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 23 | GRADLE_USER_HOME: .gradle 24 | image: openjdk:latest 25 | pull: true 26 | 27 | build: 28 | needs: [ install ] 29 | steps: 30 | - name: build 31 | commands: 32 | - ./gradlew build 33 | environment: 34 | - GRADLE_OPTS=-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 35 | - GRADLE_USER_HOME=.gradle 36 | image: openjdk:latest 37 | pull: true 38 | -------------------------------------------------------------------------------- /compiler/native/testdata/stages_pipeline.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "1" 3 | 4 | metadata: 5 | template: false 6 | 7 | environment: 8 | HELLO: "Hello, Global Environment" 9 | 10 | stages: 11 | 12 | install: 13 | steps: 14 | - name: install 15 | commands: 16 | - ./gradlew downloadDependencies 17 | environment: 18 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 19 | GRADLE_USER_HOME: .gradle 20 | image: openjdk:latest 21 | pull: true 22 | 23 | test: 24 | needs: [ install ] 25 | steps: 26 | - name: test 27 | commands: 28 | - ./gradlew check 29 | environment: 30 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 31 | GRADLE_USER_HOME: .gradle 32 | image: openjdk:latest 33 | pull: true 34 | 35 | build: 36 | needs: [ install ] 37 | steps: 38 | - name: build 39 | commands: 40 | - ./gradlew build 41 | environment: 42 | - GRADLE_OPTS=-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 43 | - GRADLE_USER_HOME=.gradle 44 | image: openjdk:latest 45 | pull: true 46 | 47 | publish: 48 | needs: [ build ] 49 | steps: 50 | - name: publish 51 | image: plugins/docker:18.09 52 | parameters: 53 | registry: index.docker.io 54 | repo: github/octocat 55 | tags: 56 | - latest 57 | - dev 58 | pull: true 59 | secrets: 60 | - source: docker_username 61 | target: registry_username 62 | - source: docker_password 63 | target: registry_password 64 | 65 | secrets: 66 | # Repo secrets 67 | - name: docker_username 68 | key: org/repo/docker/username 69 | engine: native 70 | type: repo 71 | 72 | - name: docker_password 73 | key: org/repo/docker/password 74 | engine: vault 75 | type: repo 76 | -------------------------------------------------------------------------------- /compiler/native/testdata/stages_pipeline_template.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "1" 3 | 4 | metadata: 5 | template: false 6 | 7 | templates: 8 | - name: gradle 9 | source: github.example.com/github/octocat/template.yml 10 | type: github 11 | 12 | stages: 13 | 14 | gradle: 15 | steps: 16 | # would execute the following steps: 17 | # 1. sample_get_dependencies 18 | # 2. sample_test 19 | # 3. sample_build 20 | - name: sample 21 | template: 22 | name: gradle 23 | vars: 24 | image: openjdk:latest 25 | environment: "{ GRADLE_USER_HOME: .gradle, GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false }" 26 | pull_policy: "pull: true" 27 | 28 | publish: 29 | needs: [ gradle ] 30 | steps: 31 | - name: publish 32 | image: plugins/docker:18.09 33 | parameters: 34 | registry: index.docker.io 35 | repo: github/octocat 36 | tags: 37 | - latest 38 | - dev 39 | pull: true 40 | secrets: 41 | - source: docker_username 42 | target: registry_username 43 | - source: docker_password 44 | target: registry_password 45 | 46 | secrets: 47 | # Repo secrets 48 | - name: docker_username 49 | key: org/repo/docker/username 50 | engine: native 51 | type: repo 52 | 53 | - name: docker_password 54 | key: org/repo/docker/password 55 | engine: vault 56 | type: repo 57 | -------------------------------------------------------------------------------- /compiler/native/testdata/steps.yml: -------------------------------------------------------------------------------- 1 | --- 2 | steps: 3 | - name: install 4 | commands: 5 | - ./gradlew downloadDependencies 6 | environment: 7 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 8 | GRADLE_USER_HOME: .gradle 9 | image: openjdk:latest 10 | pull: true 11 | 12 | - name: test 13 | commands: 14 | - ./gradlew check 15 | environment: 16 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 17 | GRADLE_USER_HOME: .gradle 18 | image: openjdk:latest 19 | pull: true 20 | 21 | - name: build 22 | commands: 23 | - ./gradlew build 24 | environment: 25 | - GRADLE_OPTS=-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 26 | - GRADLE_USER_HOME=.gradle 27 | image: openjdk:latest 28 | pull: true 29 | -------------------------------------------------------------------------------- /compiler/native/testdata/steps_and_stages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | stages: 3 | 4 | dependencies: 5 | steps: 6 | - name: install 7 | commands: 8 | - ./gradlew downloadDependencies 9 | environment: 10 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 11 | GRADLE_USER_HOME: .gradle 12 | image: openjdk:latest 13 | pull: true 14 | 15 | steps: 16 | 17 | - name: test 18 | commands: 19 | - ./gradlew check 20 | environment: 21 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 22 | GRADLE_USER_HOME: .gradle 23 | image: openjdk:latest 24 | pull: true 25 | 26 | - name: build 27 | commands: 28 | - ./gradlew build 29 | environment: 30 | - GRADLE_OPTS=-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 31 | - GRADLE_USER_HOME=.gradle 32 | image: openjdk:latest 33 | pull: true 34 | -------------------------------------------------------------------------------- /compiler/native/testdata/steps_pipeline.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "1" 3 | 4 | metadata: 5 | template: false 6 | 7 | environment: 8 | HELLO: "Hello, Global Environment" 9 | 10 | steps: 11 | - name: install 12 | commands: 13 | - ./gradlew downloadDependencies 14 | environment: 15 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 16 | GRADLE_USER_HOME: .gradle 17 | image: openjdk:latest 18 | pull: true 19 | 20 | - name: test 21 | commands: 22 | - ./gradlew check 23 | environment: 24 | - GRADLE_OPTS=-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 25 | - GRADLE_USER_HOME=.gradle 26 | image: openjdk:latest 27 | pull: true 28 | 29 | - name: build 30 | commands: 31 | - ./gradlew build 32 | environment: 33 | - GRADLE_OPTS=-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false 34 | - GRADLE_USER_HOME=.gradle 35 | image: openjdk:latest 36 | pull: true 37 | 38 | - name: publish 39 | image: plugins/docker:18.09 40 | parameters: 41 | registry: index.docker.io 42 | repo: github/octocat 43 | tags: 44 | - latest 45 | - dev 46 | pull: true 47 | secrets: 48 | - source: docker_username 49 | target: registry_username 50 | - source: docker_password 51 | target: registry_password 52 | 53 | secrets: 54 | # Repo secrets 55 | - name: docker_username 56 | key: org/repo/docker/username 57 | engine: native 58 | type: repo 59 | 60 | - name: docker_password 61 | key: org/repo/docker/password 62 | engine: vault 63 | type: repo 64 | -------------------------------------------------------------------------------- /compiler/native/testdata/steps_pipeline_template.yml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | 3 | metadata: 4 | os: linux 5 | 6 | templates: 7 | - name: gradle 8 | source: github.example.com/foo/bar/template.yml 9 | type: github 10 | 11 | steps: 12 | # would execute the following steps: 13 | # 1. sample_get_dependencies 14 | # 2. sample_test 15 | # 3. sample_build 16 | - name: sample 17 | template: 18 | name: gradle 19 | vars: 20 | image: openjdk:latest 21 | environment: "{ GRADLE_USER_HOME: .gradle, GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false }" 22 | pull_policy: "pull: true" 23 | 24 | - name: docker 25 | image: plugins/docker:18.09 26 | parameters: 27 | registry: index.docker.io 28 | repo: github/octocat 29 | tags: 30 | - latest 31 | - dev 32 | pull: true 33 | secrets: 34 | - source: docker_username 35 | target: registry_username 36 | - source: docker_password 37 | target: registry_password 38 | 39 | secrets: 40 | # Repo secrets 41 | - name: docker_username 42 | key: org/repo/docker/username 43 | engine: native 44 | type: repo 45 | 46 | - name: docker_password 47 | key: org/repo/docker/password 48 | engine: vault 49 | type: repo 50 | -------------------------------------------------------------------------------- /compiler/native/testdata/template-gradle.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "file", 3 | "encoding": "base64", 4 | "size": 5362, 5 | "name": "gradle.yml", 6 | "path": "gradle.yml", 7 | "content": "bWV0YWRhdGE6CiAgdGVtcGxhdGU6IHRydWUKCnN0ZXBzOgogIC0gbmFtZTogaW5zdGFsbAogICAgY29tbWFuZHM6CiAgICAgIC0gLi9ncmFkbGV3IGRvd25sb2FkRGVwZW5kZW5jaWVzCiAgICBlbnZpcm9ubWVudDoge3sgLmVudmlyb25tZW50IH19CiAgICBpbWFnZToge3sgLmltYWdlIH19CiAgICB7eyAucHVsbF9wb2xpY3kgfX0KCiAgLSBuYW1lOiB0ZXN0CiAgICBjb21tYW5kczoKICAgICAgLSAuL2dyYWRsZXcgY2hlY2sKICAgIGVudmlyb25tZW50OiB7eyAuZW52aXJvbm1lbnQgfX0KICAgIGltYWdlOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQoKICAtIG5hbWU6IGJ1aWxkCiAgICBjb21tYW5kczoKICAgICAgLSAuL2dyYWRsZXcgYnVpbGQKICAgIGVudmlyb25tZW50OiB7eyAuZW52aXJvbm1lbnQgfX0KICAgIGltYWdlOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQoKc2VjcmV0czoKICAtIG5hbWU6IGRvY2tlcl91c2VybmFtZQogICAga2V5OiBvcmcvcmVwby9mb28vYmFyCiAgICBlbmdpbmU6IG5hdGl2ZQogICAgdHlwZTogcmVwbwoKICAtIG5hbWU6IGZvb19wYXNzd29yZAogICAga2V5OiBvcmcvcmVwby9mb28vcGFzc3dvcmQKICAgIGVuZ2luZTogdmF1bHQKICAgIHR5cGU6IHJlcG8KCiAgLSBuYW1lOiB2YXVsdF90b2tlbgoKICAtIG9yaWdpbjoKICAgICAgbmFtZTogcHJpdmF0ZSB2YXVsdAogICAgICBpbWFnZTogdGFyZ2V0L3NlY3JldC12YXVsdDpsYXRlc3QKICAgICAgcHVsbDogYWx3YXlzCiAgICAgIHNlY3JldHM6IFsgdmF1bHRfdG9rZW4gXQogICAgICBwYXJhbWV0ZXJzOgogICAgICAgIGFkZHI6IHZhdWx0LmV4YW1wbGUuY29tCiAgICAgICAgYXV0aF9tZXRob2Q6IHRva2VuCiAgICAgICAgdXNlcm5hbWU6IG9jdG9jYXQKICAgICAgICBpdGVtczoKICAgICAgICAgIC0gc291cmNlOiBzZWNyZXQvZG9ja2VyCiAgICAgICAgICAgIHBhdGg6IGRvY2tlcgoKe3sgaWYgLnNlY3JldCB9fQoKICAtIG5hbWU6IGJhcl9wYXNzd29yZAogICAga2V5OiBvcmcvcmVwby9iYXIvcGFzc3dvcmQKICAgIGVuZ2luZTogdmF1bHQKICAgIHR5cGU6IHJlcG8KCnt7IGVuZCB9fQoKc2VydmljZXM6CiAgIC0gbmFtZTogcG9zdGdyZXMKICAgICBpbWFnZTogcG9zdGdyZXM6MTIKCiB7eyBpZiAuc2VydmljZSB9fQoKICAgLSBuYW1lOiByZWRpcwogICAgIGtleTogcmVkaXM6NgoKIHt7IGVuZCB9fQo=", 8 | "sha": "3d21ec53a331a6f037a91c368710b99387d012c1", 9 | "url": "https://api.github.com/repos/octokit/octokit.rb/contents/gradle.yml", 10 | "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", 11 | "html_url": "https://github.com/octokit/octokit.rb/blob/master/gradle.yml", 12 | "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/master/gradle.yml", 13 | "_links": { 14 | "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", 15 | "self": "https://api.github.com/repos/octokit/octokit.rb/contents/gradle.yml", 16 | "html": "https://github.com/octokit/octokit.rb/blob/master/gradle.yml" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /compiler/native/testdata/template-maven.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "file", 3 | "encoding": "base64", 4 | "size": 5362, 5 | "name": "maven.yml", 6 | "path": "maven.yml", 7 | "content": "bWV0YWRhdGE6CiAgdGVtcGxhdGU6IHRydWUKCnN0ZXBzOgogIC0gbmFtZTogaW5zdGFsbAogICAgY29tbWFuZHM6CiAgICAgIC0gbXZuIGRvd25sb2FkRGVwZW5kZW5jaWVzCiAgICBlbnZpcm9ubWVudDoge3sgLmVudmlyb25tZW50IH19CiAgICBpbWFnZToge3sgLmltYWdlIH19CiAgICB7eyAucHVsbF9wb2xpY3kgfX0KCiAgLSBuYW1lOiB0ZXN0CiAgICBjb21tYW5kczoKICAgICAgLSBtdm4gY2hlY2sKICAgIGVudmlyb25tZW50OiB7eyAuZW52aXJvbm1lbnQgfX0KICAgIGltYWdlOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQoKICAtIG5hbWU6IGJ1aWxkCiAgICBjb21tYW5kczoKICAgICAgLSBtdm4gYnVpbGQKICAgIGVudmlyb25tZW50OiB7eyAuZW52aXJvbm1lbnQgfX0KICAgIGltYWdlOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQoKc2VjcmV0czoKICAtIG5hbWU6IGRvY2tlcl91c2VybmFtZQogICAga2V5OiBvcmcvcmVwby9mb28vYmFyCiAgICBlbmdpbmU6IG5hdGl2ZQogICAgdHlwZTogcmVwbwoKICAtIG5hbWU6IGZvb19wYXNzd29yZAogICAga2V5OiBvcmcvcmVwby9mb28vcGFzc3dvcmQKICAgIGVuZ2luZTogdmF1bHQKICAgIHR5cGU6IHJlcG8KCiAgLSBuYW1lOiB2YXVsdF90b2tlbgoKICAtIG9yaWdpbjoKICAgICAgbmFtZTogcHJpdmF0ZSB2YXVsdAogICAgICBpbWFnZTogdGFyZ2V0L3NlY3JldC12YXVsdDpsYXRlc3QKICAgICAgcHVsbDogYWx3YXlzCiAgICAgIHNlY3JldHM6IFsgdmF1bHRfdG9rZW4gXQogICAgICBwYXJhbWV0ZXJzOgogICAgICAgIGFkZHI6IHZhdWx0LmV4YW1wbGUuY29tCiAgICAgICAgYXV0aF9tZXRob2Q6IHRva2VuCiAgICAgICAgdXNlcm5hbWU6IG9jdG9jYXQKICAgICAgICBpdGVtczoKICAgICAgICAgIC0gc291cmNlOiBzZWNyZXQvZG9ja2VyCiAgICAgICAgICAgIHBhdGg6IGRvY2tlcgoKe3sgaWYgLnNlY3JldCB9fQoKICAtIG5hbWU6IGJhcl9wYXNzd29yZAogICAga2V5OiBvcmcvcmVwby9iYXIvcGFzc3dvcmQKICAgIGVuZ2luZTogdmF1bHQKICAgIHR5cGU6IHJlcG8KCnt7IGVuZCB9fQoKc2VydmljZXM6CiAgIC0gbmFtZTogcG9zdGdyZXMKICAgICBpbWFnZTogcG9zdGdyZXM6MTIKCiB7eyBpZiAuc2VydmljZSB9fQoKICAgLSBuYW1lOiByZWRpcwogICAgIGtleTogcmVkaXM6NgoKIHt7IGVuZCB9fQo=", 8 | "sha": "3d21ec53a331a6f037a91c368710b99387d012c1", 9 | "url": "https://api.github.com/repos/octokit/octokit.rb/contents/maven.yml", 10 | "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", 11 | "html_url": "https://github.com/octokit/octokit.rb/blob/master/maven.yml", 12 | "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/master/maven.yml", 13 | "_links": { 14 | "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", 15 | "self": "https://api.github.com/repos/octokit/octokit.rb/contents/maven.yml", 16 | "html": "https://github.com/octokit/octokit.rb/blob/master/maven.yml" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /compiler/native/testdata/template-starlark.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "file", 3 | "encoding": "base64", 4 | "size": 5362, 5 | "name": "template.star", 6 | "path": "template.star", 7 | "content": "ZGVmIG1haW4oY3R4KToKICByZXR1cm4gewogICAgJ3ZlcnNpb24nOiAnMScsCiAgICAnZW52aXJvbm1lbnQnOiB7CiAgICAgICdzdGFyJzogJ3Rlc3QzJywKICAgICAgJ2Jhcic6ICd0ZXN0NCcsCiAgICB9LAogICAgJ3N0ZXBzJzogWwogICAgICB7CiAgICAgICAgJ25hbWUnOiAnYnVpbGQnLAogICAgICAgICdpbWFnZSc6ICdnb2xhbmc6bGF0ZXN0JywKICAgICAgICAnY29tbWFuZHMnOiBbCiAgICAgICAgICAnZ28gYnVpbGQnLAogICAgICAgICAgJ2dvIHRlc3QnLAogICAgICAgIF0KICAgICAgfSwKICAgIF0sCn0K\n", 8 | "sha": "3d21ec53a331a6f037a91c368710b99387d012c1", 9 | "url": "https://api.github.com/repos/octokit/octokit.rb/contents/template.star", 10 | "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", 11 | "html_url": "https://github.com/octokit/octokit.rb/blob/master/template.star", 12 | "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/master/template.star", 13 | "_links": { 14 | "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", 15 | "self": "https://api.github.com/repos/octokit/octokit.rb/contents/template.star", 16 | "html": "https://github.com/octokit/octokit.rb/blob/master/template.star" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /compiler/native/testdata/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "file", 3 | "encoding": "base64", 4 | "size": 5362, 5 | "name": "template.yml", 6 | "path": "template.yml", 7 | "content": "ZW52aXJvbm1lbnQ6CiAgc3RhcjogInRlc3QzIgogIGJhcjogInRlc3Q0IgoKbWV0YWRhdGE6CiAgdGVtcGxhdGU6IHRydWUKCnN0ZXBzOgogIC0gbmFtZTogaW5zdGFsbAogICAgY29tbWFuZHM6CiAgICAgIC0gLi9ncmFkbGV3IGRvd25sb2FkRGVwZW5kZW5jaWVzCiAgICBlbnZpcm9ubWVudDoge3sgLmVudmlyb25tZW50IH19CiAgICBpbWFnZToge3sgLmltYWdlIH19CiAgICB7eyAucHVsbF9wb2xpY3kgfX0KCiAgLSBuYW1lOiB0ZXN0CiAgICBjb21tYW5kczoKICAgICAgLSAuL2dyYWRsZXcgY2hlY2sKICAgIGVudmlyb25tZW50OiB7eyAuZW52aXJvbm1lbnQgfX0KICAgIGltYWdlOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQoKICAtIG5hbWU6IGJ1aWxkCiAgICBjb21tYW5kczoKICAgICAgLSAuL2dyYWRsZXcgYnVpbGQKICAgIGVudmlyb25tZW50OiB7eyAuZW52aXJvbm1lbnQgfX0KICAgIGltYWdlOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQoKc2VjcmV0czoKICAtIG5hbWU6IGRvY2tlcl91c2VybmFtZQogICAga2V5OiBvcmcvcmVwby9mb28vYmFyCiAgICBlbmdpbmU6IG5hdGl2ZQogICAgdHlwZTogcmVwbwoKICAtIG5hbWU6IGZvb19wYXNzd29yZAogICAga2V5OiBvcmcvcmVwby9mb28vcGFzc3dvcmQKICAgIGVuZ2luZTogdmF1bHQKICAgIHR5cGU6IHJlcG8KCnt7IGlmIC5zZWNyZXQgfX0KCiAgLSBuYW1lOiBiYXJfcGFzc3dvcmQKICAgIGtleTogb3JnL3JlcG8vYmFyL3Bhc3N3b3JkCiAgICBlbmdpbmU6IHZhdWx0CiAgICB0eXBlOiByZXBvCgp7eyBlbmQgfX0KCnNlcnZpY2VzOgogICAtIG5hbWU6IHBvc3RncmVzCiAgICAgaW1hZ2U6IHBvc3RncmVzOjEyCgoge3sgaWYgLnNlcnZpY2UgfX0KCiAgIC0gbmFtZTogcmVkaXMKICAgICBrZXk6IHJlZGlzOjYKCiB7eyBlbmQgfX0K", 8 | "sha": "3d21ec53a331a6f037a91c368710b99387d012c1", 9 | "url": "https://api.github.com/repos/octokit/octokit.rb/contents/template.yml", 10 | "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", 11 | "html_url": "https://github.com/octokit/octokit.rb/blob/master/template.yml", 12 | "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/master/template.yml", 13 | "_links": { 14 | "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", 15 | "self": "https://api.github.com/repos/octokit/octokit.rb/contents/template.yml", 16 | "html": "https://github.com/octokit/octokit.rb/blob/master/template.yml" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /compiler/native/testdata/template.star: -------------------------------------------------------------------------------- 1 | def main(ctx): 2 | return { 3 | 'version': '1', 4 | 'steps': [ 5 | { 6 | 'name': 'build', 7 | 'image': 'golang:latest', 8 | 'commands': [ 9 | 'go build', 10 | 'go test', 11 | ] 12 | }, 13 | ], 14 | } -------------------------------------------------------------------------------- /compiler/native/testdata/template.yml: -------------------------------------------------------------------------------- 1 | metadata: 2 | template: true 3 | 4 | steps: 5 | - name: install 6 | commands: 7 | - ./gradlew downloadDependencies 8 | environment: {{ .environment }} 9 | image: {{ .image }} 10 | {{ .pull_policy }} 11 | 12 | - name: test 13 | commands: 14 | - ./gradlew check 15 | environment: {{ .environment }} 16 | image: {{ .image }} 17 | {{ .pull_policy }} 18 | 19 | - name: build 20 | commands: 21 | - ./gradlew build 22 | environment: {{ .environment }} 23 | image: {{ .image }} 24 | {{ .pull_policy }} 25 | -------------------------------------------------------------------------------- /compiler/native/transform.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package native 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/go-vela/types/pipeline" 11 | "github.com/go-vela/types/yaml" 12 | ) 13 | 14 | const ( 15 | // default org for pipeline. 16 | localOrg = "localOrg" 17 | // default repo for pipeline. 18 | localRepo = "localRepo" 19 | // default build number for pipeline. 20 | localBuild = 1 21 | // default ID for pipeline. 22 | // format: `__` 23 | pipelineID = "%s_%s_%d" 24 | // default ID for steps in a stage in a pipeline. 25 | // format: `____` 26 | stageID = "%s_%s_%d_%s_%s" 27 | // default ID for steps in a pipeline. 28 | // format: `step____` 29 | stepID = "step_%s_%s_%d_%s" 30 | // default ID for services in a pipeline. 31 | // format: `service____` 32 | serviceID = "service_%s_%s_%d_%s" 33 | // default ID for secrets in a pipeline. 34 | // format: `secret____` 35 | // 36 | // nolint: gosec // ignore gosec keying off of secret as no credentials are hardcoded 37 | secretID = "secret_%s_%s_%d_%s" 38 | ) 39 | 40 | // TransformStages converts a yaml configuration with stages into an executable pipeline. 41 | func (c *client) TransformStages(r *pipeline.RuleData, p *yaml.Build) (*pipeline.Build, error) { 42 | // capture variables for setting the unique ID fields 43 | org := c.repo.GetOrg() 44 | name := c.repo.GetName() 45 | number := c.build.GetNumber() 46 | 47 | // check if the compiler is setup for a local pipeline 48 | if c.local { 49 | // check if the org provided is empty 50 | if len(org) == 0 { 51 | // set a default for the org 52 | org = localOrg 53 | } 54 | 55 | // check if the repo provided is empty 56 | if len(name) == 0 { 57 | // set a default for the repo 58 | name = localRepo 59 | } 60 | 61 | // check if the number provided is empty 62 | if number == 0 { 63 | // set a default for the number 64 | number = localBuild 65 | } 66 | } 67 | 68 | // create new executable pipeline 69 | pipeline := &pipeline.Build{ 70 | Version: p.Version, 71 | Metadata: *p.Metadata.ToPipeline(), 72 | Stages: *p.Stages.ToPipeline(), 73 | Secrets: *p.Secrets.ToPipeline(), 74 | Services: *p.Services.ToPipeline(), 75 | Worker: *p.Worker.ToPipeline(), 76 | } 77 | 78 | // set the unique ID for the executable pipeline 79 | pipeline.ID = fmt.Sprintf(pipelineID, org, name, number) 80 | 81 | // set the unique ID for each step in each stage of the executable pipeline 82 | for _, stage := range pipeline.Stages { 83 | for _, step := range stage.Steps { 84 | // create pattern for steps 85 | pattern := fmt.Sprintf(stageID, org, name, number, stage.Name, step.Name) 86 | 87 | // set id to the pattern 88 | step.ID = pattern 89 | 90 | // set the workspace directory 91 | step.Directory = step.Environment["VELA_WORKSPACE"] 92 | } 93 | } 94 | 95 | // set the unique ID for each service in the executable pipeline 96 | for _, service := range pipeline.Services { 97 | // create pattern for services 98 | pattern := fmt.Sprintf(serviceID, org, name, number, service.Name) 99 | 100 | // set id to the pattern 101 | service.ID = pattern 102 | } 103 | 104 | // set the unique ID for each secret in the executable pipeline 105 | for _, secret := range pipeline.Secrets { 106 | // skip non plugin secrets 107 | if secret.Origin.Empty() { 108 | continue 109 | } 110 | 111 | // create pattern for secrets 112 | pattern := fmt.Sprintf(secretID, org, name, number, secret.Origin.Name) 113 | 114 | // set id to the pattern 115 | secret.Origin.ID = pattern 116 | } 117 | 118 | return pipeline.Purge(r), nil 119 | } 120 | 121 | // TransformSteps converts a yaml configuration with steps into an executable pipeline. 122 | func (c *client) TransformSteps(r *pipeline.RuleData, p *yaml.Build) (*pipeline.Build, error) { 123 | // capture variables for setting the unique ID fields 124 | org := c.repo.GetOrg() 125 | name := c.repo.GetName() 126 | number := c.build.GetNumber() 127 | 128 | // check if the compiler is setup for a local pipeline 129 | if c.local { 130 | // check if the org provided is empty 131 | if len(org) == 0 { 132 | // set a default for the org 133 | org = localOrg 134 | } 135 | 136 | // check if the repo provided is empty 137 | if len(name) == 0 { 138 | // set a default for the repo 139 | name = localRepo 140 | } 141 | 142 | // check if the number provided is empty 143 | if number == 0 { 144 | // set a default for the number 145 | number = localBuild 146 | } 147 | } 148 | 149 | // create new executable pipeline 150 | pipeline := &pipeline.Build{ 151 | Version: p.Version, 152 | Metadata: *p.Metadata.ToPipeline(), 153 | Steps: *p.Steps.ToPipeline(), 154 | Secrets: *p.Secrets.ToPipeline(), 155 | Services: *p.Services.ToPipeline(), 156 | Worker: *p.Worker.ToPipeline(), 157 | } 158 | 159 | // set the unique ID for the executable pipeline 160 | pipeline.ID = fmt.Sprintf(pipelineID, org, name, number) 161 | 162 | // set the unique ID for each step in the executable pipeline 163 | for _, step := range pipeline.Steps { 164 | // create pattern for steps 165 | pattern := fmt.Sprintf(stepID, org, name, number, step.Name) 166 | 167 | // set id to the pattern 168 | step.ID = pattern 169 | 170 | // set the workspace directory 171 | step.Directory = step.Environment["VELA_WORKSPACE"] 172 | } 173 | 174 | // set the unique ID for each service in the executable pipeline 175 | for _, service := range pipeline.Services { 176 | // create pattern for services 177 | pattern := fmt.Sprintf(serviceID, org, name, number, service.Name) 178 | 179 | // set id to the pattern 180 | service.ID = pattern 181 | } 182 | 183 | // set the unique ID for each secret in the executable pipeline 184 | for _, secret := range pipeline.Secrets { 185 | // skip non plugin secrets 186 | if secret.Origin.Empty() { 187 | continue 188 | } 189 | 190 | // create pattern for secrets 191 | pattern := fmt.Sprintf(secretID, org, name, number, secret.Origin.Name) 192 | 193 | // set id to the pattern 194 | secret.Origin.ID = pattern 195 | } 196 | 197 | return pipeline.Purge(r), nil 198 | } 199 | -------------------------------------------------------------------------------- /compiler/native/validate.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package native 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/go-vela/types/yaml" 11 | ) 12 | 13 | // Validate verifies the the yaml configuration is valid. 14 | func (c *client) Validate(p *yaml.Build) error { 15 | // check a version is provided 16 | if len(p.Version) == 0 { 17 | return fmt.Errorf("no version provided") 18 | } 19 | 20 | // check that stages or steps are provided 21 | if len(p.Stages) == 0 && len(p.Steps) == 0 { 22 | return fmt.Errorf("no stages or steps provided") 23 | } 24 | 25 | // check that stages and steps aren't provided 26 | if len(p.Stages) > 0 && len(p.Steps) > 0 { 27 | return fmt.Errorf("stages and steps provided") 28 | } 29 | 30 | // validate the services block provided 31 | err := validateServices(p.Services) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | // validate the stages block provided 37 | err = validateStages(p.Stages) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | // validate the steps block provided 43 | err = validateSteps(p.Steps) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | return nil 49 | } 50 | 51 | // validateServices is a helper function that verifies the 52 | // services block in the yaml configuration is valid. 53 | func validateServices(s yaml.ServiceSlice) error { 54 | for _, service := range s { 55 | if len(service.Name) == 0 { 56 | return fmt.Errorf("no name provided for service") 57 | } 58 | 59 | if len(service.Image) == 0 { 60 | return fmt.Errorf("no image provided for service %s", service.Name) 61 | } 62 | } 63 | 64 | return nil 65 | } 66 | 67 | // validateStages is a helper function that verifies the 68 | // stages block in the yaml configuration is valid. 69 | func validateStages(s yaml.StageSlice) error { 70 | for _, stage := range s { 71 | if len(stage.Name) == 0 { 72 | return fmt.Errorf("no name provided for stage") 73 | } 74 | 75 | // validate that a stage is not referencing itself in needs 76 | for _, need := range stage.Needs { 77 | if stage.Name == need { 78 | return fmt.Errorf("stage %s references itself in 'needs' declaration", stage.Name) 79 | } 80 | } 81 | 82 | for _, step := range stage.Steps { 83 | if len(step.Name) == 0 { 84 | return fmt.Errorf("no name provided for step for stage %s", stage.Name) 85 | } 86 | 87 | // nolint: lll // ignore simplification here 88 | if len(step.Image) == 0 && len(step.Template.Name) == 0 { 89 | return fmt.Errorf("no image or template provided for step %s for stage %s", step.Name, stage.Name) 90 | } 91 | 92 | if step.Name == "clone" || step.Name == "init" { 93 | continue 94 | } 95 | 96 | // nolint: lll // ignore simplification here 97 | if len(step.Commands) == 0 && len(step.Environment) == 0 && 98 | len(step.Parameters) == 0 && len(step.Secrets) == 0 && 99 | len(step.Template.Name) == 0 && !step.Detach { 100 | return fmt.Errorf("no commands, environment, parameters, secrets or template provided for step %s for stage %s", step.Name, stage.Name) 101 | } 102 | } 103 | } 104 | 105 | return nil 106 | } 107 | 108 | // validateSteps is a helper function that verifies the 109 | // steps block in the yaml configuration is valid. 110 | func validateSteps(s yaml.StepSlice) error { 111 | for _, step := range s { 112 | if len(step.Name) == 0 { 113 | return fmt.Errorf("no name provided for step") 114 | } 115 | 116 | if len(step.Image) == 0 && len(step.Template.Name) == 0 { 117 | return fmt.Errorf("no image or template provided for step %s", step.Name) 118 | } 119 | 120 | if step.Name == "clone" || step.Name == "init" { 121 | continue 122 | } 123 | 124 | // nolint: lll // ignore simplification here 125 | if len(step.Commands) == 0 && len(step.Environment) == 0 && 126 | len(step.Parameters) == 0 && len(step.Secrets) == 0 && 127 | len(step.Template.Name) == 0 && !step.Detach { 128 | return fmt.Errorf("no commands, environment, parameters, secrets or template provided for step %s", step.Name) 129 | } 130 | } 131 | 132 | return nil 133 | } 134 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-vela/compiler 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Masterminds/sprig/v3 v3.2.2 7 | github.com/buildkite/yaml v0.0.0-20181016232759-0caa5f0796e3 8 | github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect 9 | github.com/drone/envsubst v1.0.3 10 | github.com/gin-gonic/gin v1.7.4 11 | github.com/go-vela/types v0.10.0 12 | github.com/google/go-cmp v0.5.6 13 | github.com/google/go-github/v39 v39.2.0 14 | github.com/google/uuid v1.1.4 // indirect 15 | github.com/goware/urlx v0.3.1 16 | github.com/hashicorp/go-cleanhttp v0.5.2 17 | github.com/hashicorp/go-retryablehttp v0.7.0 18 | github.com/huandu/xstrings v1.3.2 // indirect 19 | github.com/mitchellh/reflectwalk v1.0.1 // indirect 20 | github.com/sirupsen/logrus v1.8.1 21 | github.com/spf13/afero v1.6.0 22 | github.com/ugorji/go v1.1.11 // indirect 23 | github.com/urfave/cli/v2 v2.3.0 24 | go.starlark.net v0.0.0-20211013185944-b0039bd2cfe3 25 | golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f 26 | k8s.io/apimachinery v0.22.2 27 | ) 28 | -------------------------------------------------------------------------------- /registry/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | // Package registry provides the ability for Vela to 6 | // integrate with different supported Template registries. 7 | // 8 | // Usage: 9 | // 10 | // import "github.com/go-vela/compiler/registry" 11 | package registry 12 | -------------------------------------------------------------------------------- /registry/github/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | // Package github provides the ability for Vela to 6 | // integrate with GitHub or GitHub Enterprise as a 7 | // template registry. 8 | // 9 | // Usage: 10 | // 11 | // import "github.com/go-vela/compiler/registry/github" 12 | package github 13 | -------------------------------------------------------------------------------- /registry/github/github.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package github 6 | 7 | import ( 8 | "context" 9 | "net/url" 10 | "strings" 11 | 12 | "github.com/google/go-github/v39/github" 13 | "golang.org/x/oauth2" 14 | ) 15 | 16 | const ( 17 | defaultURL = "https://github.com/" // Default GitHub URL 18 | defaultAPI = "https://api.github.com/" // Default GitHub API URL 19 | ) 20 | 21 | type client struct { 22 | Github *github.Client 23 | URL string 24 | API string 25 | } 26 | 27 | // New returns a Registry implementation that integrates 28 | // with GitHub or a GitHub Enterprise instance. 29 | // 30 | // nolint: revive // ignore returning unexported client 31 | func New(address, token string) (*client, error) { 32 | // create the client object 33 | c := &client{ 34 | URL: defaultURL, 35 | API: defaultAPI, 36 | } 37 | 38 | // ensure we have the URL and API set 39 | if len(address) > 0 { 40 | if !strings.EqualFold(c.URL, address) { 41 | c.URL = strings.Trim(address, "/") 42 | if !strings.Contains(c.URL, "https://github.com") { 43 | c.API = c.URL + "/api/v3/" 44 | } 45 | } 46 | } 47 | 48 | // create the GitHub client 49 | gitClient := github.NewClient(nil) 50 | // ensure the proper URL is set 51 | gitClient.BaseURL, _ = url.Parse(c.API) 52 | 53 | if len(token) > 0 { 54 | // create GitHub OAuth client with user's token 55 | gitClient = c.newClientToken(token) 56 | } 57 | 58 | // overwrite the github client 59 | c.Github = gitClient 60 | 61 | return c, nil 62 | } 63 | 64 | // newClientToken is a helper function to return the GitHub oauth2 client. 65 | func (c *client) newClientToken(token string) *github.Client { 66 | // create the token object for the client 67 | ts := oauth2.StaticTokenSource( 68 | &oauth2.Token{AccessToken: token}, 69 | ) 70 | 71 | // create the OAuth client 72 | tc := oauth2.NewClient(context.Background(), ts) 73 | // if c.SkipVerify { 74 | // tc.Transport.(*oauth2.Transport).Base = &http.Transport{ 75 | // Proxy: http.ProxyFromEnvironment, 76 | // TLSClientConfig: &tls.Config{ 77 | // InsecureSkipVerify: true, 78 | // }, 79 | // } 80 | // } 81 | 82 | // create the GitHub client from the OAuth client 83 | github := github.NewClient(tc) 84 | 85 | // ensure the proper URL is set 86 | github.BaseURL, _ = url.Parse(c.API) 87 | return github 88 | } 89 | -------------------------------------------------------------------------------- /registry/github/github_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package github 6 | 7 | import ( 8 | "context" 9 | "net/http" 10 | "net/http/httptest" 11 | "net/url" 12 | "reflect" 13 | "testing" 14 | 15 | "github.com/google/go-github/v39/github" 16 | "golang.org/x/oauth2" 17 | ) 18 | 19 | func TestGithub_New(t *testing.T) { 20 | // setup router 21 | s := httptest.NewServer(http.NotFoundHandler()) 22 | defer s.Close() 23 | 24 | gitClient := github.NewClient(nil) 25 | gitClient.BaseURL, _ = url.Parse(s.URL + "/api/v3/") 26 | 27 | want := &client{ 28 | Github: gitClient, 29 | URL: s.URL, 30 | API: s.URL + "/api/v3/", 31 | } 32 | 33 | // run test 34 | got, err := New(s.URL, "") 35 | 36 | if err != nil { 37 | t.Errorf("New returned err: %v", err) 38 | } 39 | 40 | if !reflect.DeepEqual(got, want) { 41 | t.Errorf("New is %v, want %v", got, want) 42 | } 43 | } 44 | 45 | func TestGithub_NewToken(t *testing.T) { 46 | // setup router 47 | s := httptest.NewServer(http.NotFoundHandler()) 48 | defer s.Close() 49 | 50 | token := "foobar" 51 | ts := oauth2.StaticTokenSource( 52 | &oauth2.Token{AccessToken: token}, 53 | ) 54 | tc := oauth2.NewClient(context.Background(), ts) 55 | gitClient := github.NewClient(tc) 56 | gitClient.BaseURL, _ = url.Parse(s.URL + "/api/v3/") 57 | 58 | want := &client{ 59 | Github: gitClient, 60 | URL: s.URL, 61 | API: s.URL + "/api/v3/", 62 | } 63 | 64 | // run test 65 | got, err := New(s.URL, token) 66 | 67 | if err != nil { 68 | t.Errorf("New returned err: %v", err) 69 | } 70 | 71 | if !reflect.DeepEqual(got, want) { 72 | t.Errorf("New is %+v, want %+v", got.Github, want.Github) 73 | } 74 | } 75 | 76 | func TestGithub_NewURL(t *testing.T) { 77 | // setup tests 78 | tests := []struct { 79 | address string 80 | want client 81 | }{ 82 | { 83 | // address matches default, so no change to default URL or API. 84 | address: "https://github.com/", 85 | want: client{ 86 | URL: "https://github.com/", 87 | API: "https://api.github.com/", 88 | }, 89 | }, 90 | { 91 | // not the default address, but has github.com, so keep default API. 92 | address: "https://github.com", 93 | want: client{ 94 | URL: "https://github.com", 95 | API: "https://api.github.com/", 96 | }, 97 | }, 98 | { 99 | // github-enterprise install with / 100 | address: "https://git.example.com/", 101 | want: client{ 102 | URL: "https://git.example.com", 103 | API: "https://git.example.com/api/v3/", 104 | }, 105 | }, 106 | { 107 | // github-enterprise install without / 108 | address: "https://git.example.com", 109 | want: client{ 110 | URL: "https://git.example.com", 111 | API: "https://git.example.com/api/v3/", 112 | }, 113 | }, 114 | } 115 | 116 | // run tests 117 | for _, test := range tests { 118 | // run test 119 | got, err := New(test.address, "foobar") 120 | 121 | if err != nil { 122 | t.Errorf("New returned err: %v", err) 123 | } 124 | 125 | if got.URL != test.want.URL { 126 | t.Errorf("New URL is %v, want %v", got.URL, test.want.URL) 127 | } 128 | if got.API != test.want.API { 129 | t.Errorf("New API is %v, want %v", got.API, test.want.API) 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /registry/github/parse.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package github 6 | 7 | import ( 8 | "fmt" 9 | "strings" 10 | 11 | "github.com/go-vela/compiler/registry" 12 | 13 | "github.com/goware/urlx" 14 | ) 15 | 16 | // Parse creates the registry source object from 17 | // a template path and default branch. 18 | func (c *client) Parse(path string) (*registry.Source, error) { 19 | // ref will hold the reference identifier, 20 | // eg. //@ 21 | ref := "" 22 | 23 | // parse the path provided 24 | // 25 | // goware/urlx is used over net/url because it is more consistent for parsing 26 | // the template paths we use (similar to go imports) 27 | u, err := urlx.Parse(path) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | u.Path = strings.TrimPrefix(u.Path, "/") 33 | 34 | // this will handle multiple cases for the path: 35 | // * // 36 | // * //// 37 | // nolint: gomnd // ignore magic number 38 | parts := strings.SplitN(u.Path, "/", 3) 39 | 40 | // ensure org, repo and filename parts exist 41 | // nolint: gomnd // ignore magic number 42 | if len(parts) < 3 { 43 | // nolint: lll // ignore long line length due to error message 44 | return ®istry.Source{}, fmt.Errorf("invalid template source %s, must contain org/repo/path_to_template", path) 45 | } 46 | 47 | // check for reference provided in filename: 48 | // * //@ 49 | // * ////@ 50 | if strings.Contains(parts[2], "@") { 51 | // capture the filename and reference 52 | refParts := strings.Split(parts[2], "@") 53 | // set the filename 54 | parts[2] = refParts[0] 55 | // set the reference 56 | ref = refParts[1] 57 | } 58 | 59 | return ®istry.Source{ 60 | Host: u.Host, 61 | Org: parts[0], 62 | Repo: parts[1], 63 | Name: parts[2], 64 | Ref: ref, 65 | }, nil 66 | } 67 | -------------------------------------------------------------------------------- /registry/github/parse_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package github 6 | 7 | import ( 8 | "fmt" 9 | "net/http" 10 | "net/http/httptest" 11 | "net/url" 12 | "reflect" 13 | "testing" 14 | 15 | "github.com/go-vela/compiler/registry" 16 | ) 17 | 18 | func TestGithub_Parse(t *testing.T) { 19 | // setup mock server 20 | s := httptest.NewServer(http.NotFoundHandler()) 21 | defer s.Close() 22 | 23 | // setup types 24 | want := ®istry.Source{ 25 | Host: "github.example.com", 26 | Org: "github", 27 | Repo: "octocat", 28 | Name: "template.yml", 29 | Ref: "", 30 | } 31 | 32 | // run test 33 | c, err := New(s.URL, "") 34 | if err != nil { 35 | t.Errorf("Creating client returned err: %v", err) 36 | } 37 | 38 | path := "github.example.com/github/octocat/template.yml" 39 | 40 | got, err := c.Parse(path) 41 | if err != nil { 42 | t.Errorf("Parse returned err: %v", err) 43 | } 44 | 45 | if !reflect.DeepEqual(got, want) { 46 | t.Errorf("Parse is %v, want %v", got, want) 47 | } 48 | } 49 | 50 | func TestGithub_ParseWithBranch(t *testing.T) { 51 | // setup mock server 52 | s := httptest.NewServer(http.NotFoundHandler()) 53 | defer s.Close() 54 | 55 | // setup types 56 | want := ®istry.Source{ 57 | Host: "github.example.com", 58 | Org: "github", 59 | Repo: "octocat", 60 | Name: "template.yml", 61 | Ref: "dev", 62 | } 63 | 64 | // run test 65 | c, err := New(s.URL, "") 66 | if err != nil { 67 | t.Errorf("Creating client returned err: %v", err) 68 | } 69 | 70 | path := "github.example.com/github/octocat/template.yml@dev" 71 | 72 | got, err := c.Parse(path) 73 | if err != nil { 74 | t.Errorf("Parse returned err: %v", err) 75 | } 76 | 77 | if !reflect.DeepEqual(got, want) { 78 | t.Errorf("Parse is %v, want %v", got, want) 79 | } 80 | } 81 | func TestGithub_Parse_Custom(t *testing.T) { 82 | // setup mock server 83 | s := httptest.NewServer(http.NotFoundHandler()) 84 | defer s.Close() 85 | 86 | // setup types 87 | want := ®istry.Source{ 88 | Host: "github.example.com", 89 | Org: "github", 90 | Repo: "octocat", 91 | Name: "path/to/template.yml", 92 | Ref: "test", 93 | } 94 | 95 | // run test 96 | c, err := New(s.URL, "") 97 | if err != nil { 98 | t.Errorf("Creating client returned err: %v", err) 99 | } 100 | 101 | path := "github.example.com/github/octocat/path/to/template.yml@test" 102 | 103 | got, err := c.Parse(path) 104 | if err != nil { 105 | t.Errorf("Parse returned err: %v", err) 106 | } 107 | 108 | if !reflect.DeepEqual(got, want) { 109 | t.Errorf("Parse is %v, want %v", got, want) 110 | } 111 | } 112 | 113 | func TestGithub_Parse_Full(t *testing.T) { 114 | // setup mock server 115 | s := httptest.NewServer(http.NotFoundHandler()) 116 | defer s.Close() 117 | 118 | // setup types 119 | u, err := url.Parse(s.URL) 120 | if err != nil { 121 | t.Errorf("Parsing url returned err: %v", err) 122 | } 123 | 124 | want := ®istry.Source{ 125 | Host: u.Host, 126 | Org: "github", 127 | Repo: "octocat", 128 | Name: "template.yml", 129 | Ref: "test", 130 | } 131 | 132 | // run test 133 | c, err := New(s.URL, "") 134 | if err != nil { 135 | t.Errorf("Creating client returned err: %v", err) 136 | } 137 | 138 | path := fmt.Sprintf("%s/%s", s.URL, "github/octocat/template.yml@test") 139 | 140 | got, err := c.Parse(path) 141 | if err != nil { 142 | t.Errorf("Parse returned err: %v", err) 143 | } 144 | 145 | if !reflect.DeepEqual(got, want) { 146 | t.Errorf("Parse is %v, want %v", got, want) 147 | } 148 | } 149 | 150 | func TestGithub_Parse_Invalid(t *testing.T) { 151 | // setup mock server 152 | s := httptest.NewServer(http.NotFoundHandler()) 153 | defer s.Close() 154 | 155 | // run test 156 | c, err := New(s.URL, "") 157 | if err != nil { 158 | t.Errorf("Creating client returned err: %v", err) 159 | } 160 | 161 | got, err := c.Parse("!@#$%^&*()") 162 | if err == nil { 163 | t.Errorf("Parse should have returned err") 164 | } 165 | 166 | if got != nil { 167 | t.Errorf("Parse is %v, want nil", got) 168 | } 169 | } 170 | 171 | func TestGithub_Parse_Hostname(t *testing.T) { 172 | // setup mock server 173 | s := httptest.NewServer(http.NotFoundHandler()) 174 | defer s.Close() 175 | 176 | // setup types 177 | u, err := url.Parse(s.URL) 178 | if err != nil { 179 | t.Errorf("Parsing url returned err: %v", err) 180 | } 181 | 182 | want := ®istry.Source{ 183 | Host: u.Hostname(), 184 | Org: "github", 185 | Repo: "octocat", 186 | Name: "template.yml", 187 | Ref: "", 188 | } 189 | 190 | // run test 191 | c, err := New(s.URL, "") 192 | if err != nil { 193 | t.Errorf("Creating client returned err: %v", err) 194 | } 195 | 196 | path := fmt.Sprintf("%s/%s", u.Hostname(), "github/octocat/template.yml") 197 | 198 | got, err := c.Parse(path) 199 | if err != nil { 200 | t.Errorf("Parse returned err: %v", err) 201 | } 202 | 203 | if !reflect.DeepEqual(got, want) { 204 | t.Errorf("Parse is %v, want %v", got, want) 205 | } 206 | } 207 | 208 | func TestGithub_Parse_Path(t *testing.T) { 209 | // setup mock server 210 | s := httptest.NewServer(http.NotFoundHandler()) 211 | defer s.Close() 212 | 213 | // setup types 214 | u, err := url.Parse(s.URL) 215 | if err != nil { 216 | t.Errorf("Parsing url returned err: %v", err) 217 | } 218 | 219 | want := ®istry.Source{ 220 | Host: u.Host, 221 | Org: "github", 222 | Repo: "octocat", 223 | Name: "path/to/template.yml", 224 | Ref: "", 225 | } 226 | 227 | // run test 228 | c, err := New(s.URL, "") 229 | if err != nil { 230 | t.Errorf("Creating client returned err: %v", err) 231 | } 232 | 233 | path := fmt.Sprintf("%s/%s", s.URL, "github/octocat/path/to/template.yml") 234 | 235 | got, err := c.Parse(path) 236 | if err != nil { 237 | t.Errorf("Parse returned err: %v", err) 238 | } 239 | 240 | if !reflect.DeepEqual(got, want) { 241 | t.Errorf("Parse is %v, want %v", got, want) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /registry/github/template.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package github 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "net/http" 11 | 12 | "github.com/go-vela/compiler/registry" 13 | 14 | "github.com/go-vela/types/library" 15 | 16 | "github.com/google/go-github/v39/github" 17 | ) 18 | 19 | // Template captures the templated pipeline configuration from the GitHub repo. 20 | func (c *client) Template(u *library.User, s *registry.Source) ([]byte, error) { 21 | // use default GitHub OAuth client we provide 22 | cli := c.Github 23 | if u != nil { 24 | // create GitHub OAuth client with user's token 25 | cli = c.newClientToken(u.GetToken()) 26 | } 27 | 28 | // create the options to pass 29 | opts := &github.RepositoryContentGetOptions{} 30 | 31 | // set the reference for the options to capture the templated pipeline 32 | // configuration. if no ref is set, it will pull from the default 33 | // branch on the targeted repo, see: 34 | // https://docs.github.com/en/rest/reference/repos#get-repository-content--parameters 35 | if len(s.Ref) > 0 { 36 | opts.Ref = s.Ref 37 | } 38 | 39 | // send API call to capture the templated pipeline configuration 40 | // 41 | // nolint: lll // ignore long line length due to variable names 42 | data, _, resp, err := cli.Repositories.GetContents(context.Background(), s.Org, s.Repo, s.Name, opts) 43 | if err != nil { 44 | if resp != nil && resp.StatusCode != http.StatusNotFound { 45 | // return different error message depending on if a branch was provided 46 | if len(s.Ref) == 0 { 47 | errString := "unexpected error fetching template %s/%s/%s: %v" 48 | return nil, fmt.Errorf(errString, s.Org, s.Repo, s.Name, err) 49 | } 50 | errString := "unexpected error fetching template %s/%s/%s@%s: %v" 51 | return nil, fmt.Errorf(errString, s.Org, s.Repo, s.Name, s.Ref, err) 52 | } 53 | 54 | // return different error message depending on if a branch was provided 55 | if len(s.Ref) == 0 { 56 | return nil, fmt.Errorf("no Vela template found at %s/%s/%s", s.Org, s.Repo, s.Name) 57 | } 58 | return nil, fmt.Errorf("no Vela template found at %s/%s/%s@%s", s.Org, s.Repo, s.Name, s.Ref) 59 | } 60 | 61 | // data is not nil if template exists 62 | if data != nil { 63 | strData, err := data.GetContent() 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | return []byte(strData), nil 69 | } 70 | 71 | // return different error message depending on if a branch was provided 72 | if len(s.Ref) == 0 { 73 | return nil, fmt.Errorf("no Vela template found at %s/%s/%s", s.Org, s.Repo, s.Name) 74 | } 75 | return nil, fmt.Errorf("no Vela template found at %s/%s/%s@%s", s.Org, s.Repo, s.Name, s.Ref) 76 | } 77 | -------------------------------------------------------------------------------- /registry/github/template_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package github 6 | 7 | import ( 8 | "io/ioutil" 9 | "net/http" 10 | "net/http/httptest" 11 | "reflect" 12 | "testing" 13 | 14 | "github.com/go-vela/compiler/registry" 15 | 16 | "github.com/go-vela/types/library" 17 | 18 | "github.com/gin-gonic/gin" 19 | ) 20 | 21 | func TestGithub_Template(t *testing.T) { 22 | // setup context 23 | gin.SetMode(gin.TestMode) 24 | resp := httptest.NewRecorder() 25 | _, engine := gin.CreateTestContext(resp) 26 | 27 | // setup mock server 28 | engine.GET("/api/v3/repos/:owner/:name/contents/:path", func(c *gin.Context) { 29 | c.Header("Content-Type", "application/json") 30 | c.Status(http.StatusOK) 31 | c.File("testdata/template.json") 32 | }) 33 | s := httptest.NewServer(engine) 34 | defer s.Close() 35 | 36 | // setup types 37 | str := "foo" 38 | u := &library.User{ 39 | Name: &str, 40 | Token: &str, 41 | } 42 | 43 | src := ®istry.Source{ 44 | Org: "github", 45 | Repo: "octocat", 46 | Name: "template.yml", 47 | } 48 | 49 | want, err := ioutil.ReadFile("testdata/template.yml") 50 | if err != nil { 51 | t.Errorf("Reading file returned err: %v", err) 52 | } 53 | 54 | // run test 55 | c, err := New(s.URL, "") 56 | if err != nil { 57 | t.Errorf("Creating client returned err: %v", err) 58 | } 59 | 60 | got, err := c.Template(u, src) 61 | 62 | if resp.Code != http.StatusOK { 63 | t.Errorf("Template returned %v, want %v", resp.Code, http.StatusOK) 64 | } 65 | 66 | if err != nil { 67 | t.Errorf("Template returned err: %v", err) 68 | } 69 | 70 | if !reflect.DeepEqual(got, want) { 71 | t.Errorf("Template is %v, want %v", got, want) 72 | } 73 | } 74 | 75 | func TestGithub_TemplateSourceRef(t *testing.T) { 76 | // setup context 77 | gin.SetMode(gin.TestMode) 78 | resp := httptest.NewRecorder() 79 | _, engine := gin.CreateTestContext(resp) 80 | 81 | // store the ref 82 | gotRef := "" 83 | 84 | // setup mock server 85 | engine.GET("/api/v3/repos/:owner/:name/contents/:path", func(c *gin.Context) { 86 | gotRef = c.Request.FormValue("ref") 87 | c.Header("Content-Type", "application/json") 88 | c.Status(http.StatusOK) 89 | c.File("testdata/template.json") 90 | }) 91 | s := httptest.NewServer(engine) 92 | defer s.Close() 93 | 94 | // setup types 95 | str := "foo" 96 | u := &library.User{ 97 | Name: &str, 98 | Token: &str, 99 | } 100 | 101 | src := ®istry.Source{ 102 | Org: "github", 103 | Repo: "octocat", 104 | Name: "template.yml", 105 | Ref: "main", 106 | } 107 | 108 | want, err := ioutil.ReadFile("testdata/template.yml") 109 | if err != nil { 110 | t.Errorf("Reading file returned err: %v", err) 111 | } 112 | 113 | // run test 114 | c, err := New(s.URL, "") 115 | if err != nil { 116 | t.Errorf("Creating client returned err: %v", err) 117 | } 118 | 119 | got, err := c.Template(u, src) 120 | 121 | if resp.Code != http.StatusOK { 122 | t.Errorf("Template returned %v, want %v", resp.Code, http.StatusOK) 123 | } 124 | 125 | if err != nil { 126 | t.Errorf("Template returned err: %v", err) 127 | } 128 | 129 | if gotRef != src.Ref { 130 | t.Errorf("Ref returned %v, want %v", gotRef, src.Ref) 131 | } 132 | 133 | if !reflect.DeepEqual(got, want) { 134 | t.Errorf("Template is %v, want %v", got, want) 135 | } 136 | } 137 | 138 | func TestGithub_TemplateEmptySourceRef(t *testing.T) { 139 | // setup context 140 | gin.SetMode(gin.TestMode) 141 | resp := httptest.NewRecorder() 142 | _, engine := gin.CreateTestContext(resp) 143 | 144 | // store the ref 145 | gotRef := "" 146 | 147 | // setup mock server 148 | engine.GET("/api/v3/repos/:owner/:name/contents/:path", func(c *gin.Context) { 149 | gotRef = c.Request.FormValue("ref") 150 | c.Header("Content-Type", "application/json") 151 | c.Status(http.StatusOK) 152 | c.File("testdata/template.json") 153 | }) 154 | s := httptest.NewServer(engine) 155 | defer s.Close() 156 | 157 | // setup types 158 | str := "foo" 159 | u := &library.User{ 160 | Name: &str, 161 | Token: &str, 162 | } 163 | 164 | src := ®istry.Source{ 165 | Org: "github", 166 | Repo: "octocat", 167 | Name: "template.yml", 168 | } 169 | 170 | want, err := ioutil.ReadFile("testdata/template.yml") 171 | if err != nil { 172 | t.Errorf("Reading file returned err: %v", err) 173 | } 174 | 175 | // run test 176 | c, err := New(s.URL, "") 177 | if err != nil { 178 | t.Errorf("Creating client returned err: %v", err) 179 | } 180 | 181 | got, err := c.Template(u, src) 182 | 183 | if resp.Code != http.StatusOK { 184 | t.Errorf("Template returned %v, want %v", resp.Code, http.StatusOK) 185 | } 186 | 187 | if err != nil { 188 | t.Errorf("Template returned err: %v", err) 189 | } 190 | 191 | if gotRef != "" { 192 | t.Errorf("Ref returned %v, want empty string", gotRef) 193 | } 194 | 195 | if !reflect.DeepEqual(got, want) { 196 | t.Errorf("Template is %v, want %v", got, want) 197 | } 198 | } 199 | 200 | func TestGithub_Template_BadRequest(t *testing.T) { 201 | // setup context 202 | gin.SetMode(gin.TestMode) 203 | resp := httptest.NewRecorder() 204 | _, engine := gin.CreateTestContext(resp) 205 | 206 | // setup mock server 207 | engine.GET("/api/v3/repos/foo/bar/contents/:path", func(c *gin.Context) { 208 | c.Status(http.StatusBadRequest) 209 | }) 210 | s := httptest.NewServer(engine) 211 | defer s.Close() 212 | 213 | // setup types 214 | str := "foo" 215 | u := &library.User{ 216 | Name: &str, 217 | Token: &str, 218 | } 219 | 220 | src := ®istry.Source{ 221 | Org: "github", 222 | Repo: "octocat", 223 | Name: "template.yml", 224 | } 225 | 226 | // run test 227 | c, err := New(s.URL, "") 228 | if err != nil { 229 | t.Errorf("Creating client returned err: %v", err) 230 | } 231 | 232 | got, err := c.Template(u, src) 233 | 234 | if resp.Code != http.StatusOK { 235 | t.Errorf("Template returned %v, want %v", resp.Code, http.StatusOK) 236 | } 237 | 238 | if err == nil { 239 | t.Error("Template should have returned err") 240 | } 241 | 242 | if got != nil { 243 | t.Errorf("Template is %v, want nil", got) 244 | } 245 | } 246 | 247 | func TestGithub_Template_NotFound(t *testing.T) { 248 | // setup context 249 | gin.SetMode(gin.TestMode) 250 | resp := httptest.NewRecorder() 251 | _, engine := gin.CreateTestContext(resp) 252 | 253 | // setup mock server 254 | engine.GET("/api/v3/repos/foo/bar/contents/:path", func(c *gin.Context) { 255 | c.Status(http.StatusNotFound) 256 | }) 257 | s := httptest.NewServer(engine) 258 | defer s.Close() 259 | 260 | // setup types 261 | str := "foo" 262 | u := &library.User{ 263 | Name: &str, 264 | Token: &str, 265 | } 266 | 267 | src := ®istry.Source{ 268 | Org: "github", 269 | Repo: "octocat", 270 | Name: "template.yml", 271 | } 272 | 273 | // run test 274 | c, err := New(s.URL, "") 275 | if err != nil { 276 | t.Errorf("Creating client returned err: %v", err) 277 | } 278 | 279 | got, err := c.Template(u, src) 280 | 281 | if resp.Code != http.StatusOK { 282 | t.Errorf("Template returned %v, want %v", resp.Code, http.StatusOK) 283 | } 284 | 285 | if err == nil { 286 | t.Error("Template should have returned err") 287 | } 288 | 289 | if got != nil { 290 | t.Errorf("Template is %v, want nil", got) 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /registry/github/testdata/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "file", 3 | "encoding": "base64", 4 | "size": 5362, 5 | "name": "template.yml", 6 | "path": "template.yml", 7 | "content": "bWV0YWRhdGE6CiAgdGVtcGxhdGU6IHRydWUKCnN0ZXBzOgogIC0gbmFtZTogZ2V0X2RlcGVuZGVuY2llcwogICAgcGx1Z2luOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQogICAgZW52aXJvbm1lbnQ6IHt7IC5lbnZpcm9ubWVudCB9fQogICAgY29tbWFuZHM6CiAgICAgIC0gLi9ncmFkbGV3IGRvd25sb2FkRGVwZW5kZW5jaWVzCgogIC0gbmFtZTogdGVzdAogICAgcGx1Z2luOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQogICAgZW52aXJvbm1lbnQ6IHt7IC5lbnZpcm9ubWVudCB9fQogICAgY29tbWFuZHM6CiAgICAgIC0gLi9ncmFkbGV3IGNoZWNrCgogIC0gbmFtZTogYnVpbGQKICAgIHBsdWdpbjoge3sgLmltYWdlIH19CiAgICB7eyAucHVsbF9wb2xpY3kgfX0KICAgIGVudmlyb25tZW50OiB7eyAuZW52aXJvbm1lbnQgfX0KICAgIGNvbW1hbmRzOgogICAgICAtIC4vZ3JhZGxldyBidWlsZCBkaXN0VGFyCg==", 8 | "sha": "3d21ec53a331a6f037a91c368710b99387d012c1", 9 | "url": "https://api.github.com/repos/octokit/octokit.rb/contents/template.yml", 10 | "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", 11 | "html_url": "https://github.com/octokit/octokit.rb/blob/master/template.yml", 12 | "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/master/template.yml", 13 | "_links": { 14 | "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", 15 | "self": "https://api.github.com/repos/octokit/octokit.rb/contents/template.yml", 16 | "html": "https://github.com/octokit/octokit.rb/blob/master/template.yml" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /registry/github/testdata/template.yml: -------------------------------------------------------------------------------- 1 | metadata: 2 | template: true 3 | 4 | steps: 5 | - name: get_dependencies 6 | plugin: {{ .image }} 7 | {{ .pull_policy }} 8 | environment: {{ .environment }} 9 | commands: 10 | - ./gradlew downloadDependencies 11 | 12 | - name: test 13 | plugin: {{ .image }} 14 | {{ .pull_policy }} 15 | environment: {{ .environment }} 16 | commands: 17 | - ./gradlew check 18 | 19 | - name: build 20 | plugin: {{ .image }} 21 | {{ .pull_policy }} 22 | environment: {{ .environment }} 23 | commands: 24 | - ./gradlew build distTar 25 | -------------------------------------------------------------------------------- /registry/registry.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package registry 6 | 7 | import "github.com/go-vela/types/library" 8 | 9 | // Service represents the interface for Vela integrating 10 | // with the different supported template registries. 11 | type Service interface { 12 | // Parse defines a function that creates the 13 | // registry source object from a template path. 14 | Parse(string) (*Source, error) 15 | 16 | // Template defines a function that captures the 17 | // templated pipeline configuration from a repo. 18 | Template(*library.User, *Source) ([]byte, error) 19 | } 20 | -------------------------------------------------------------------------------- /registry/source.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package registry 6 | 7 | // Source represents a registry object 8 | // for retrieving templates. 9 | type Source struct { 10 | Host string 11 | Org string 12 | Repo string 13 | Name string 14 | Ref string 15 | } 16 | -------------------------------------------------------------------------------- /template/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | // Package template provides the ability for Vela to 6 | // render a templated yaml configuration into an 7 | // executable pipeline. 8 | // 9 | // Usage: 10 | // 11 | // import "github.com/go-vela/compiler/template" 12 | package template 13 | -------------------------------------------------------------------------------- /template/native/convert.go: -------------------------------------------------------------------------------- 1 | package native 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/go-vela/types/raw" 7 | ) 8 | 9 | // convertPlatformVars takes the platform injected variables 10 | // within the step environment block and modifies them to be returned 11 | // within the template. 12 | func convertPlatformVars(slice raw.StringSliceMap, name string) raw.StringSliceMap { 13 | envs := make(map[string]string) 14 | for key, value := range slice { 15 | key = strings.ToLower(key) 16 | if strings.HasPrefix(key, "vela_") { 17 | envs[strings.TrimPrefix(key, "vela_")] = value 18 | } 19 | } 20 | 21 | envs["template_name"] = name 22 | 23 | return envs 24 | } 25 | 26 | type funcHandler struct { 27 | envs raw.StringSliceMap 28 | } 29 | 30 | // returnPlatformVar returns the value of the platform 31 | // variable if it exists within the environment map. 32 | func (h funcHandler) returnPlatformVar(input string) string { 33 | input = strings.ToLower(input) 34 | input = strings.TrimPrefix(input, "vela_") 35 | // check if key exists within map 36 | if _, ok := h.envs[input]; ok { 37 | // return value if exists 38 | return h.envs[input] 39 | } 40 | // return empty string if not exists 41 | return "" 42 | } 43 | -------------------------------------------------------------------------------- /template/native/convert_test.go: -------------------------------------------------------------------------------- 1 | package native 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/go-vela/types/raw" 8 | ) 9 | 10 | func Test_convertPlatformVars(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | slice raw.StringSliceMap 14 | templateName string 15 | want raw.StringSliceMap 16 | }{ 17 | { 18 | name: "with all vela prefixed vars", 19 | slice: raw.StringSliceMap{ 20 | "VELA_BUILD_AUTHOR": "octocat", 21 | "VELA_REPO_FULL_NAME": "go-vela/hello-world", 22 | "VELA_USER_ADMIN": "true", 23 | "VELA_WORKSPACE": "/vela/src/github.com/go-vela/hello-world", 24 | }, 25 | templateName: "foo", 26 | want: raw.StringSliceMap{"build_author": "octocat", "repo_full_name": "go-vela/hello-world", "user_admin": "true", "workspace": "/vela/src/github.com/go-vela/hello-world", "template_name": "foo"}, 27 | }, 28 | { 29 | name: "with combination of vela and user vars", 30 | slice: raw.StringSliceMap{ 31 | "VELA_BUILD_AUTHOR": "octocat", 32 | "VELA_REPO_FULL_NAME": "go-vela/hello-world", 33 | "FOO_VAR1": "test1", 34 | "BAR_VAR1": "test2", 35 | }, 36 | templateName: "foo", 37 | want: raw.StringSliceMap{"build_author": "octocat", "repo_full_name": "go-vela/hello-world", "template_name": "foo"}, 38 | }, 39 | } 40 | for _, tt := range tests { 41 | t.Run(tt.name, func(t *testing.T) { 42 | if got := convertPlatformVars(tt.slice, tt.templateName); !reflect.DeepEqual(got, tt.want) { 43 | t.Errorf("convertPlatformVars() = %v, want %v", got, tt.want) 44 | } 45 | }) 46 | } 47 | } 48 | 49 | func Test_funcHandler_returnPlatformVar(t *testing.T) { 50 | type fields struct { 51 | envs raw.StringSliceMap 52 | } 53 | type args struct { 54 | input string 55 | } 56 | tests := []struct { 57 | name string 58 | fields fields 59 | args args 60 | want string 61 | }{ 62 | { 63 | name: "existing platform var without prefix (lowercase)", 64 | fields: fields{ 65 | envs: raw.StringSliceMap{ 66 | "build_author": "octocat", 67 | }, 68 | }, 69 | args: args{input: "build_author"}, 70 | want: "octocat", 71 | }, 72 | { 73 | name: "existing platform var without prefix (uppercase)", 74 | fields: fields{ 75 | envs: raw.StringSliceMap{ 76 | "build_author": "octocat", 77 | }, 78 | }, 79 | args: args{input: "BUILD_AUTHOR"}, 80 | want: "octocat", 81 | }, 82 | { 83 | name: "existing platform var with prefix (lowercase)", 84 | fields: fields{ 85 | envs: raw.StringSliceMap{ 86 | "build_author": "octocat", 87 | }, 88 | }, 89 | args: args{input: "vela_build_author"}, 90 | want: "octocat", 91 | }, 92 | { 93 | name: "existing platform var with prefix (uppercase)", 94 | fields: fields{ 95 | envs: raw.StringSliceMap{ 96 | "build_author": "octocat", 97 | }, 98 | }, 99 | args: args{input: "VELA_BUILD_AUTHOR"}, 100 | want: "octocat", 101 | }, 102 | { 103 | name: "non existent var", 104 | fields: fields{ 105 | envs: raw.StringSliceMap{ 106 | "build_author": "octocat", 107 | }, 108 | }, 109 | args: args{input: "foo"}, 110 | want: "", 111 | }, 112 | } 113 | for _, tt := range tests { 114 | t.Run(tt.name, func(t *testing.T) { 115 | h := funcHandler{ 116 | envs: tt.fields.envs, 117 | } 118 | if got := h.returnPlatformVar(tt.args.input); got != tt.want { 119 | t.Errorf("returnPlatformVar() = %v, want %v", got, tt.want) 120 | } 121 | }) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /template/native/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | // Package native provides the ability for Vela to 6 | // render a templated yaml configuration into an 7 | // executable pipeline. 8 | // 9 | // Usage: 10 | // 11 | // import "github.com/go-vela/compiler/template/native" 12 | package native 13 | -------------------------------------------------------------------------------- /template/native/render.go: -------------------------------------------------------------------------------- 1 | package native 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "text/template" 7 | 8 | "github.com/go-vela/types/raw" 9 | 10 | types "github.com/go-vela/types/yaml" 11 | 12 | "github.com/Masterminds/sprig/v3" 13 | 14 | yaml "github.com/buildkite/yaml" 15 | ) 16 | 17 | // RenderStep combines the template with the step in the yaml pipeline. 18 | // nolint: lll // ignore long line length due to return args 19 | func RenderStep(tmpl string, s *types.Step) (types.StepSlice, types.SecretSlice, types.ServiceSlice, raw.StringSliceMap, error) { 20 | buffer := new(bytes.Buffer) 21 | config := new(types.Build) 22 | 23 | velaFuncs := funcHandler{envs: convertPlatformVars(s.Environment, s.Name)} 24 | templateFuncMap := map[string]interface{}{ 25 | "vela": velaFuncs.returnPlatformVar, 26 | } 27 | // modify Masterminds/sprig functions 28 | // to remove OS functions 29 | // 30 | // https://masterminds.github.io/sprig/os.html 31 | sf := sprig.TxtFuncMap() 32 | delete(sf, "env") 33 | delete(sf, "expandenv") 34 | 35 | // parse the template with Masterminds/sprig functions 36 | // 37 | // https://pkg.go.dev/github.com/Masterminds/sprig?tab=doc#TxtFuncMap 38 | t, err := template.New(s.Name).Funcs(sf).Funcs(templateFuncMap).Parse(tmpl) 39 | if err != nil { 40 | // nolint: lll // ignore long line length due to return arguments 41 | return types.StepSlice{}, types.SecretSlice{}, types.ServiceSlice{}, raw.StringSliceMap{}, fmt.Errorf("unable to parse template %s: %v", s.Template.Name, err) 42 | } 43 | 44 | // apply the variables to the parsed template 45 | err = t.Execute(buffer, s.Template.Variables) 46 | if err != nil { 47 | // nolint: lll // ignore long line length due to return arguments 48 | return types.StepSlice{}, types.SecretSlice{}, types.ServiceSlice{}, raw.StringSliceMap{}, fmt.Errorf("unable to execute template %s: %v", s.Template.Name, err) 49 | } 50 | 51 | // unmarshal the template to the pipeline 52 | err = yaml.Unmarshal(buffer.Bytes(), config) 53 | if err != nil { 54 | // nolint: lll // ignore long line length due to return args 55 | return types.StepSlice{}, types.SecretSlice{}, types.ServiceSlice{}, raw.StringSliceMap{}, fmt.Errorf("unable to unmarshal yaml: %v", err) 56 | } 57 | 58 | // ensure all templated steps have template prefix 59 | for index, newStep := range config.Steps { 60 | config.Steps[index].Name = fmt.Sprintf("%s_%s", s.Name, newStep.Name) 61 | } 62 | 63 | return config.Steps, config.Secrets, config.Services, config.Environment, nil 64 | } 65 | 66 | // RenderBuild renders the templated build. 67 | func RenderBuild(b string, envs map[string]string) (*types.Build, error) { 68 | buffer := new(bytes.Buffer) 69 | config := new(types.Build) 70 | 71 | velaFuncs := funcHandler{envs: convertPlatformVars(envs, "")} 72 | templateFuncMap := map[string]interface{}{ 73 | "vela": velaFuncs.returnPlatformVar, 74 | } 75 | // modify Masterminds/sprig functions 76 | // to remove OS functions 77 | // 78 | // https://masterminds.github.io/sprig/os.html 79 | sf := sprig.TxtFuncMap() 80 | delete(sf, "env") 81 | delete(sf, "expandenv") 82 | 83 | // parse the template with Masterminds/sprig functions 84 | // 85 | // https://pkg.go.dev/github.com/Masterminds/sprig?tab=doc#TxtFuncMap 86 | t, err := template.New("build").Funcs(sf).Funcs(templateFuncMap).Parse(b) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | // execute the template 92 | err = t.Execute(buffer, "") 93 | if err != nil { 94 | return nil, fmt.Errorf("unable to execute template: %w", err) 95 | } 96 | 97 | // unmarshal the template to the pipeline 98 | err = yaml.Unmarshal(buffer.Bytes(), config) 99 | if err != nil { 100 | return nil, fmt.Errorf("unable to unmarshal yaml: %w", err) 101 | } 102 | 103 | return config, nil 104 | } 105 | -------------------------------------------------------------------------------- /template/native/render_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package native 6 | 7 | import ( 8 | "io/ioutil" 9 | "testing" 10 | 11 | goyaml "github.com/buildkite/yaml" 12 | "github.com/google/go-cmp/cmp" 13 | 14 | "github.com/go-vela/types/raw" 15 | "github.com/go-vela/types/yaml" 16 | ) 17 | 18 | func TestNative_RenderStep(t *testing.T) { 19 | type args struct { 20 | velaFile string 21 | templateFile string 22 | } 23 | tests := []struct { 24 | name string 25 | args args 26 | wantFile string 27 | wantErr bool 28 | }{ 29 | {"basic", args{velaFile: "testdata/step/basic/step.yml", templateFile: "testdata/step/basic/tmpl.yml"}, "testdata/step/basic/want.yml", false}, 30 | {"multiline", args{velaFile: "testdata/step/multiline/step.yml", templateFile: "testdata/step/multiline/tmpl.yml"}, "testdata/step/multiline/want.yml", false}, 31 | {"conditional match", args{velaFile: "testdata/step/conditional/step.yml", templateFile: "testdata/step/conditional/tmpl.yml"}, "testdata/step/conditional/want.yml", false}, 32 | {"loop map", args{velaFile: "testdata/step/loop_map/step.yml", templateFile: "testdata/step/loop_map/tmpl.yml"}, "testdata/step/loop_map/want.yml", false}, 33 | {"loop slice", args{velaFile: "testdata/step/loop_slice/step.yml", templateFile: "testdata/step/loop_slice/tmpl.yml"}, "testdata/step/loop_slice/want.yml", false}, 34 | {"platform vars", args{velaFile: "testdata/step/with_vars_plat/step.yml", templateFile: "testdata/step/with_vars_plat/tmpl.yml"}, "testdata/step/with_vars_plat/want.yml", false}, 35 | {"invalid template", args{velaFile: "testdata/step/basic/step.yml", templateFile: "testdata/step/invalid_template.yml"}, "", true}, 36 | {"invalid variable", args{velaFile: "testdata/step/basic/step.yml", templateFile: "testdata/step/invalid_variables.yml"}, "", true}, 37 | {"invalid yml", args{velaFile: "testdata/step/basic/step.yml", templateFile: "testdata/step/invalid.yml"}, "", true}, 38 | {"disallowed env func", args{velaFile: "testdata/step/basic/step.yml", templateFile: "testdata/step/disallowed/tmpl_env.yml"}, "", true}, 39 | {"disallowed expandenv func", args{velaFile: "testdata/step/basic/step.yml", templateFile: "testdata/step/disallowed/tmpl_expandenv.yml"}, "", true}, 40 | } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | sFile, err := ioutil.ReadFile(tt.args.velaFile) 44 | if err != nil { 45 | t.Error(err) 46 | } 47 | b := &yaml.Build{} 48 | err = goyaml.Unmarshal(sFile, b) 49 | if err != nil { 50 | t.Error(err) 51 | } 52 | b.Steps[0].Environment = raw.StringSliceMap{ 53 | "VELA_REPO_FULL_NAME": "octocat/hello-world", 54 | } 55 | 56 | tmpl, err := ioutil.ReadFile(tt.args.templateFile) 57 | if err != nil { 58 | t.Error(err) 59 | } 60 | 61 | steps, secrets, services, environment, err := RenderStep(string(tmpl), b.Steps[0]) 62 | if (err != nil) != tt.wantErr { 63 | t.Errorf("RenderStep() error = %v, wantErr %v", err, tt.wantErr) 64 | return 65 | } 66 | 67 | if tt.wantErr != true { 68 | wFile, err := ioutil.ReadFile(tt.wantFile) 69 | if err != nil { 70 | t.Error(err) 71 | } 72 | w := &yaml.Build{} 73 | err = goyaml.Unmarshal(wFile, w) 74 | if err != nil { 75 | t.Error(err) 76 | } 77 | wantSteps := w.Steps 78 | wantSecrets := w.Secrets 79 | wantServices := w.Services 80 | wantEnvironment := w.Environment 81 | 82 | if diff := cmp.Diff(wantSteps, steps); diff != "" { 83 | t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff) 84 | } 85 | if diff := cmp.Diff(wantSecrets, secrets); diff != "" { 86 | t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff) 87 | } 88 | if diff := cmp.Diff(wantServices, services); diff != "" { 89 | t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff) 90 | } 91 | if diff := cmp.Diff(wantEnvironment, environment); diff != "" { 92 | t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff) 93 | } 94 | } 95 | }) 96 | } 97 | } 98 | 99 | func TestNative_RenderBuild(t *testing.T) { 100 | type args struct { 101 | velaFile string 102 | } 103 | tests := []struct { 104 | name string 105 | args args 106 | wantFile string 107 | wantErr bool 108 | }{ 109 | {"steps", args{velaFile: "testdata/build/basic/build.yml"}, "testdata/build/basic/want.yml", false}, 110 | {"stages", args{velaFile: "testdata/build/basic_stages/build.yml"}, "testdata/build/basic_stages/want.yml", false}, 111 | {"conditional match", args{velaFile: "testdata/build/conditional/build.yml"}, "testdata/build/conditional/want.yml", false}, 112 | } 113 | for _, tt := range tests { 114 | t.Run(tt.name, func(t *testing.T) { 115 | sFile, err := ioutil.ReadFile(tt.args.velaFile) 116 | if err != nil { 117 | t.Error(err) 118 | } 119 | 120 | got, err := RenderBuild(string(sFile), map[string]string{ 121 | "VELA_REPO_FULL_NAME": "octocat/hello-world", 122 | "VELA_BUILD_BRANCH": "master", 123 | }) 124 | if (err != nil) != tt.wantErr { 125 | t.Errorf("RenderBuild() error = %v, wantErr %v", err, tt.wantErr) 126 | return 127 | } 128 | 129 | if tt.wantErr != true { 130 | wFile, err := ioutil.ReadFile(tt.wantFile) 131 | if err != nil { 132 | t.Error(err) 133 | } 134 | want := &yaml.Build{} 135 | err = goyaml.Unmarshal(wFile, want) 136 | if err != nil { 137 | t.Error(err) 138 | } 139 | 140 | if diff := cmp.Diff(want, got); diff != "" { 141 | t.Errorf("RenderBuild() mismatch (-want +got):\n%s", diff) 142 | } 143 | } 144 | }) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /template/native/testdata/build/basic/build.yml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | 3 | {{$steplist := list "foo" "bar" "star"}} 4 | 5 | steps: 6 | {{range $step := $steplist}} 7 | - name: {{ $step }} 8 | image: alpine 9 | commands: 10 | - echo hello from {{ $step }} 11 | {{ end }} -------------------------------------------------------------------------------- /template/native/testdata/build/basic/want.yml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | 3 | steps: 4 | - name: foo 5 | image: alpine 6 | pull: not_present 7 | commands: 8 | - echo hello from foo 9 | - name: bar 10 | image: alpine 11 | pull: not_present 12 | commands: 13 | - echo hello from bar 14 | - name: star 15 | image: alpine 16 | pull: not_present 17 | commands: 18 | - echo hello from star -------------------------------------------------------------------------------- /template/native/testdata/build/basic_stages/build.yml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | 3 | {{$stageList := list "foo" "bar" "star" -}} 4 | 5 | stages: 6 | {{range $stage := $stageList -}} 7 | {{ $stage }}: 8 | steps: 9 | - name: {{ $stage }} 10 | image: alpine 11 | commands: 12 | - echo hello from {{ $stage }} 13 | {{ end }} 14 | -------------------------------------------------------------------------------- /template/native/testdata/build/basic_stages/want.yml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | 3 | stages: 4 | foo: 5 | steps: 6 | - name: foo 7 | image: alpine 8 | commands: 9 | - echo hello from foo 10 | needs: [ "clone" ] 11 | pull: not_present 12 | 13 | bar: 14 | steps: 15 | - name: bar 16 | image: alpine 17 | commands: 18 | - echo hello from bar 19 | needs: [ "clone" ] 20 | pull: not_present 21 | 22 | star: 23 | steps: 24 | - name: star 25 | image: alpine 26 | commands: 27 | - echo hello from star 28 | needs: [ "clone" ] 29 | pull: not_present 30 | 31 | -------------------------------------------------------------------------------- /template/native/testdata/build/conditional/build.yml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | 3 | {{$br := vela "vela_build_branch"}} 4 | {{$image := "golang:latest"}} 5 | 6 | steps: 7 | 8 | {{ if (eq $br "master") }} 9 | 10 | - name: install 11 | commands: 12 | - go get ./... 13 | image: {{ $image }} 14 | ruleset: 15 | event: [ push, pull_request ] 16 | 17 | {{ end }} 18 | 19 | - name: test 20 | commands: 21 | - go test ./... 22 | image: {{ $image }} 23 | ruleset: 24 | event: [ push, pull_request ] 25 | 26 | - name: build 27 | commands: 28 | - go build 29 | environment: 30 | CGO_ENABLED: '0' 31 | GOOS: linux 32 | image: {{ $image }} 33 | ruleset: 34 | event: [ push, pull_request ] 35 | -------------------------------------------------------------------------------- /template/native/testdata/build/conditional/want.yml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | steps: 3 | - name: install 4 | commands: 5 | - go get ./... 6 | image: golang:latest 7 | ruleset: 8 | event: [ push, pull_request ] 9 | 10 | - name: test 11 | commands: 12 | - go test ./... 13 | image: golang:latest 14 | ruleset: 15 | event: [ push, pull_request ] 16 | 17 | - name: build 18 | commands: 19 | - go build 20 | environment: 21 | CGO_ENABLED: '0' 22 | GOOS: linux 23 | image: golang:latest 24 | ruleset: 25 | event: [ push, pull_request ] 26 | -------------------------------------------------------------------------------- /template/native/testdata/step/basic/step.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample 3 | template: 4 | name: golang 5 | vars: 6 | image: golang:latest 7 | pull_policy: "pull: true" 8 | 9 | -------------------------------------------------------------------------------- /template/native/testdata/step/basic/tmpl.yml: -------------------------------------------------------------------------------- 1 | metadata: 2 | template: true 3 | 4 | steps: 5 | - name: install 6 | commands: 7 | - go get ./... 8 | image: {{ .image }} 9 | {{ .pull_policy }} 10 | ruleset: 11 | event: [ push, pull_request ] 12 | 13 | - name: test 14 | commands: 15 | - go test ./... 16 | image: {{ .image }} 17 | {{ .pull_policy }} 18 | ruleset: 19 | event: [ push, pull_request ] 20 | 21 | - name: build 22 | commands: 23 | - go build 24 | environment: 25 | CGO_ENABLED: '0' 26 | GOOS: linux 27 | image: {{ .image }} 28 | {{ .pull_policy }} 29 | ruleset: 30 | event: [ push, pull_request ] 31 | -------------------------------------------------------------------------------- /template/native/testdata/step/basic/want.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample_install 3 | commands: 4 | - go get ./... 5 | image: golang:latest 6 | pull: true 7 | ruleset: 8 | event: [ push, pull_request ] 9 | 10 | - name: sample_test 11 | commands: 12 | - go test ./... 13 | image: golang:latest 14 | pull: true 15 | ruleset: 16 | event: [ push, pull_request ] 17 | 18 | - name: sample_build 19 | commands: 20 | - go build 21 | environment: 22 | CGO_ENABLED: '0' 23 | GOOS: linux 24 | image: golang:latest 25 | pull: true 26 | ruleset: 27 | event: [ push, pull_request ] -------------------------------------------------------------------------------- /template/native/testdata/step/conditional/step.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample 3 | template: 4 | name: golang 5 | vars: 6 | image: golang:latest 7 | pull_policy: "pull: true" 8 | branch: master 9 | 10 | -------------------------------------------------------------------------------- /template/native/testdata/step/conditional/tmpl.yml: -------------------------------------------------------------------------------- 1 | metadata: 2 | template: true 3 | 4 | {{$br := .branch}} 5 | 6 | steps: 7 | 8 | {{ if (eq $br "master") }} 9 | 10 | - name: install 11 | commands: 12 | - go get ./... 13 | image: {{ .image }} 14 | {{ .pull_policy }} 15 | ruleset: 16 | event: [ push, pull_request ] 17 | 18 | {{ end }} 19 | 20 | - name: test 21 | commands: 22 | - go test ./... 23 | image: {{ .image }} 24 | {{ .pull_policy }} 25 | ruleset: 26 | event: [ push, pull_request ] 27 | 28 | - name: build 29 | commands: 30 | - go build 31 | environment: 32 | CGO_ENABLED: '0' 33 | GOOS: linux 34 | image: {{ .image }} 35 | {{ .pull_policy }} 36 | ruleset: 37 | event: [ push, pull_request ] 38 | -------------------------------------------------------------------------------- /template/native/testdata/step/conditional/want.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample_install 3 | commands: 4 | - go get ./... 5 | image: golang:latest 6 | pull: true 7 | ruleset: 8 | event: [ push, pull_request ] 9 | 10 | - name: sample_test 11 | commands: 12 | - go test ./... 13 | image: golang:latest 14 | pull: true 15 | ruleset: 16 | event: [ push, pull_request ] 17 | 18 | - name: sample_build 19 | commands: 20 | - go build 21 | environment: 22 | CGO_ENABLED: '0' 23 | GOOS: linux 24 | image: golang:latest 25 | pull: true 26 | ruleset: 27 | event: [ push, pull_request ] -------------------------------------------------------------------------------- /template/native/testdata/step/disallowed/tmpl_env.yml: -------------------------------------------------------------------------------- 1 | metadata: 2 | template: true 3 | 4 | steps: 5 | - name: echo 6 | commands: 7 | - echo {{ env "VELA_SOURCE_CLIENT" }} 8 | image: alpine:latest -------------------------------------------------------------------------------- /template/native/testdata/step/disallowed/tmpl_expandenv.yml: -------------------------------------------------------------------------------- 1 | metadata: 2 | template: true 3 | 4 | steps: 5 | - name: echo 6 | commands: 7 | - echo {{ expandenv "Your client id is set to $VELA_SOURCE_CLIENT" }} 8 | image: alpine:latest -------------------------------------------------------------------------------- /template/native/testdata/step/invalid.yml: -------------------------------------------------------------------------------- 1 | --- 2 | !@#$%^&*() -------------------------------------------------------------------------------- /template/native/testdata/step/invalid_template.yml: -------------------------------------------------------------------------------- 1 | --- 2 | {{ foobar }} 3 | -------------------------------------------------------------------------------- /template/native/testdata/step/invalid_variables.yml: -------------------------------------------------------------------------------- 1 | --- 2 | {{template "foobar"}} 3 | -------------------------------------------------------------------------------- /template/native/testdata/step/loop_map/step.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample 3 | template: 4 | name: golang 5 | vars: 6 | pull_policy: "pull: true" 7 | # loop over map of images 8 | images: 9 | latest: golang:latest 10 | 11 | -------------------------------------------------------------------------------- /template/native/testdata/step/loop_map/tmpl.yml: -------------------------------------------------------------------------------- 1 | metadata: 2 | template: true 3 | 4 | ## template variables 5 | {{ $pull := .pull_policy }} 6 | 7 | steps: 8 | 9 | - name: install 10 | commands: 11 | - go get ./... 12 | image: golang:latest 13 | {{ $pull }} 14 | ruleset: 15 | event: [ push, pull_request ] 16 | 17 | {{ range $key, $value := .images }} 18 | 19 | - name: test_{{ $key }} 20 | commands: 21 | - go test ./... 22 | image: {{ $value }} 23 | {{ $pull }} 24 | ruleset: 25 | event: [ push, pull_request ] 26 | 27 | {{ end }} 28 | 29 | - name: build 30 | commands: 31 | - go build 32 | environment: 33 | CGO_ENABLED: '0' 34 | GOOS: linux 35 | image: golang:latest 36 | {{ $pull }} 37 | ruleset: 38 | event: [ push, pull_request ] 39 | -------------------------------------------------------------------------------- /template/native/testdata/step/loop_map/want.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample_install 3 | commands: 4 | - go get ./... 5 | image: golang:latest 6 | pull: true 7 | ruleset: 8 | event: [ push, pull_request ] 9 | 10 | - name: sample_test_latest 11 | commands: 12 | - go test ./... 13 | image: golang:latest 14 | pull: true 15 | ruleset: 16 | event: [ push, pull_request ] 17 | 18 | - name: sample_build 19 | commands: 20 | - go build 21 | environment: 22 | CGO_ENABLED: '0' 23 | GOOS: linux 24 | image: golang:latest 25 | pull: true 26 | ruleset: 27 | event: [ push, pull_request ] -------------------------------------------------------------------------------- /template/native/testdata/step/loop_slice/step.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample 3 | template: 4 | name: golang 5 | vars: 6 | pull_policy: "pull: true" 7 | # loop over slice of images 8 | images: [ golang:latest, golang:1.12, golang:1.13 ] 9 | 10 | -------------------------------------------------------------------------------- /template/native/testdata/step/loop_slice/tmpl.yml: -------------------------------------------------------------------------------- 1 | metadata: 2 | template: true 3 | 4 | ## template variables 5 | {{ $pull := .pull_policy }} 6 | 7 | steps: 8 | 9 | - name: install 10 | commands: 11 | - go get ./... 12 | image: golang:latest 13 | {{ $pull }} 14 | ruleset: 15 | event: [ push, pull_request ] 16 | 17 | {{ range $value := .images }} 18 | 19 | - name: test_{{ $value }} 20 | commands: 21 | - go test ./... 22 | image: {{ $value }} 23 | {{ $pull }} 24 | ruleset: 25 | event: [ push, pull_request ] 26 | 27 | {{ end }} 28 | 29 | - name: build 30 | commands: 31 | - go build 32 | environment: 33 | CGO_ENABLED: '0' 34 | GOOS: linux 35 | image: golang:latest 36 | {{ $pull }} 37 | ruleset: 38 | event: [ push, pull_request ] 39 | -------------------------------------------------------------------------------- /template/native/testdata/step/loop_slice/want.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample_install 3 | commands: 4 | - go get ./... 5 | image: golang:latest 6 | pull: true 7 | ruleset: 8 | event: [ push, pull_request ] 9 | 10 | - name: sample_test_golang:latest 11 | commands: 12 | - go test ./... 13 | image: golang:latest 14 | pull: true 15 | ruleset: 16 | event: [ push, pull_request ] 17 | 18 | - name: sample_test_golang:1.12 19 | commands: 20 | - go test ./... 21 | image: golang:1.12 22 | pull: true 23 | ruleset: 24 | event: [ push, pull_request ] 25 | 26 | - name: sample_test_golang:1.13 27 | commands: 28 | - go test ./... 29 | image: golang:1.13 30 | pull: true 31 | ruleset: 32 | event: [ push, pull_request ] 33 | 34 | - name: sample_build 35 | commands: 36 | - go build 37 | environment: 38 | CGO_ENABLED: '0' 39 | GOOS: linux 40 | image: golang:latest 41 | pull: true 42 | ruleset: 43 | event: [ push, pull_request ] -------------------------------------------------------------------------------- /template/native/testdata/step/multiline/step.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample 3 | template: 4 | name: golang 5 | vars: 6 | test: | 7 | - name: test 8 | commands: 9 | - go test ./... 10 | image: golang:latest 11 | pull: true 12 | ruleset: 13 | event: [ push, pull_request ] 14 | 15 | -------------------------------------------------------------------------------- /template/native/testdata/step/multiline/tmpl.yml: -------------------------------------------------------------------------------- 1 | metadata: 2 | template: true 3 | 4 | steps: 5 | - name: install 6 | commands: 7 | - go get ./... 8 | image: golang:latest 9 | pull: true 10 | ruleset: 11 | event: [ push, pull_request ] 12 | 13 | {{ .test }} 14 | 15 | - name: build 16 | commands: 17 | - go build 18 | environment: 19 | CGO_ENABLED: '0' 20 | GOOS: linux 21 | image: golang:latest 22 | pull: true 23 | ruleset: 24 | event: [ push, pull_request ] 25 | -------------------------------------------------------------------------------- /template/native/testdata/step/multiline/want.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample_install 3 | commands: 4 | - go get ./... 5 | image: golang:latest 6 | pull: true 7 | ruleset: 8 | event: [ push, pull_request ] 9 | 10 | - name: sample_test 11 | commands: 12 | - go test ./... 13 | image: golang:latest 14 | pull: true 15 | ruleset: 16 | event: [ push, pull_request ] 17 | 18 | - name: sample_build 19 | commands: 20 | - go build 21 | environment: 22 | CGO_ENABLED: '0' 23 | GOOS: linux 24 | image: golang:latest 25 | pull: true 26 | ruleset: 27 | event: [ push, pull_request ] -------------------------------------------------------------------------------- /template/native/testdata/step/with_vars_plat/step.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample 3 | template: 4 | name: golang -------------------------------------------------------------------------------- /template/native/testdata/step/with_vars_plat/tmpl.yml: -------------------------------------------------------------------------------- 1 | metadata: 2 | template: true 3 | 4 | steps: 5 | - name: test 6 | commands: 7 | - echo {{ vela "repo_full_name" }} 8 | - echo {{ vela "REPO_FULL_NAME" }} 9 | - echo {{ vela "vela_repo_full_name" }} 10 | - echo {{ vela "VELA_REPO_FULL_NAME" }} 11 | - echo {{ vela "non_existent" }} 12 | image: alpine 13 | pull: true 14 | ruleset: 15 | event: [ push, pull_request ] -------------------------------------------------------------------------------- /template/native/testdata/step/with_vars_plat/want.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample_test 3 | commands: 4 | - echo octocat/hello-world 5 | - echo octocat/hello-world 6 | - echo octocat/hello-world 7 | - echo octocat/hello-world 8 | - echo 9 | image: alpine 10 | pull: true 11 | ruleset: 12 | event: [ push, pull_request ] -------------------------------------------------------------------------------- /template/starlark/convert.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package starlark 6 | 7 | import ( 8 | "strings" 9 | 10 | "github.com/go-vela/types/raw" 11 | "go.starlark.net/starlark" 12 | ) 13 | 14 | // convertTemplateVars takes template variables and converts 15 | // them to a starlark string dictionary for template reference. 16 | // 17 | // Example Usage within template: ctx["vars"]["message"] = "Hello, World!" 18 | // 19 | // Explanation of type "starlark.StringDict": 20 | // https://pkg.go.dev/go.starlark.net/starlark#StringDict 21 | func convertTemplateVars(m map[string]interface{}) (*starlark.Dict, error) { 22 | dict := starlark.NewDict(0) 23 | 24 | // loop through user vars converting provided types to starlark primitives 25 | for key, value := range m { 26 | val, err := toStarlark(value) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | err = dict.SetKey(starlark.String(key), val) 32 | if err != nil { 33 | return nil, err 34 | } 35 | } 36 | 37 | return dict, nil 38 | } 39 | 40 | // convertPlatformVars takes the platform injected variables 41 | // within the step environment block and converts them to a 42 | // starlark string dictionary. 43 | // 44 | // Example Usage within template: ctx["vela"]["build"]["number"] = 1 45 | // 46 | // Explanation of type "starlark.StringDict": 47 | // https://pkg.go.dev/go.starlark.net/starlark#StringDict 48 | func convertPlatformVars(slice raw.StringSliceMap, name string) (*starlark.Dict, error) { 49 | build := starlark.NewDict(0) 50 | repo := starlark.NewDict(0) 51 | user := starlark.NewDict(0) 52 | system := starlark.NewDict(0) 53 | dict := starlark.NewDict(0) 54 | 55 | err := dict.SetKey(starlark.String("build"), build) 56 | if err != nil { 57 | return nil, err 58 | } 59 | err = dict.SetKey(starlark.String("repo"), repo) 60 | if err != nil { 61 | return nil, err 62 | } 63 | err = dict.SetKey(starlark.String("user"), user) 64 | if err != nil { 65 | return nil, err 66 | } 67 | err = dict.SetKey(starlark.String("system"), system) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | err = system.SetKey(starlark.String("template_name"), starlark.String(name)) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | for key, value := range slice { 78 | key = strings.ToLower(key) 79 | if strings.HasPrefix(key, "vela_") { 80 | key = strings.TrimPrefix(key, "vela_") 81 | 82 | switch { 83 | case strings.HasPrefix(key, "build_"): 84 | err := build.SetKey(starlark.String(strings.TrimPrefix(key, "build_")), starlark.String(value)) 85 | if err != nil { 86 | return nil, err 87 | } 88 | case strings.HasPrefix(key, "repo_"): 89 | err := repo.SetKey(starlark.String(strings.TrimPrefix(key, "repo_")), starlark.String(value)) 90 | if err != nil { 91 | return nil, err 92 | } 93 | case strings.HasPrefix(key, "user_"): 94 | err := user.SetKey(starlark.String(strings.TrimPrefix(key, "user_")), starlark.String(value)) 95 | if err != nil { 96 | return nil, err 97 | } 98 | default: 99 | err := system.SetKey(starlark.String(key), starlark.String(value)) 100 | if err != nil { 101 | return nil, err 102 | } 103 | } 104 | } 105 | } 106 | 107 | return dict, nil 108 | } 109 | -------------------------------------------------------------------------------- /template/starlark/convert_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package starlark 6 | 7 | import ( 8 | "reflect" 9 | "testing" 10 | 11 | "github.com/go-vela/types/raw" 12 | "go.starlark.net/starlark" 13 | ) 14 | 15 | func TestStarlark_Render_convertTemplateVars(t *testing.T) { 16 | // setup types 17 | tags := starlark.Tuple(nil) 18 | tags = append(tags, starlark.String("latest")) 19 | tags = append(tags, starlark.String("1.14")) 20 | tags = append(tags, starlark.String("1.15")) 21 | 22 | commands := starlark.NewDict(16) 23 | err := commands.SetKey(starlark.String("test"), starlark.String("go test ./...")) 24 | if err != nil { 25 | t.Error(err) 26 | } 27 | 28 | strWant := starlark.NewDict(0) 29 | err = strWant.SetKey(starlark.String("pull"), starlark.String("always")) 30 | if err != nil { 31 | t.Error(err) 32 | } 33 | 34 | arrayWant := starlark.NewDict(0) 35 | err = arrayWant.SetKey(starlark.String("tags"), tags) 36 | if err != nil { 37 | t.Error(err) 38 | } 39 | 40 | mapWant := starlark.NewDict(0) 41 | err = mapWant.SetKey(starlark.String("commands"), commands) 42 | if err != nil { 43 | t.Error(err) 44 | } 45 | 46 | tests := []struct { 47 | name string 48 | args map[string]interface{} 49 | want *starlark.Dict 50 | }{ 51 | { 52 | name: "test for a user passed string", 53 | args: map[string]interface{}{"pull": "always"}, 54 | want: strWant, 55 | }, 56 | { 57 | name: "test for a user passed array", 58 | args: map[string]interface{}{"tags": []string{"latest", "1.14", "1.15"}}, 59 | want: arrayWant, 60 | }, 61 | { 62 | name: "test for a user passed map", 63 | args: map[string]interface{}{"commands": map[string]string{"test": "go test ./..."}}, 64 | want: mapWant, 65 | }} 66 | 67 | for _, tt := range tests { 68 | t.Run(tt.name, func(t *testing.T) { 69 | got, err := convertTemplateVars(tt.args) 70 | if err != nil { 71 | t.Error(err) 72 | } 73 | 74 | if !reflect.DeepEqual(got, tt.want) { 75 | t.Errorf("convertTemplateVars() = %v, want %v", got, tt.want) 76 | } 77 | }) 78 | } 79 | } 80 | 81 | func TestStarlark_Render_velaEnvironmentData(t *testing.T) { 82 | // setup types 83 | build := starlark.NewDict(1) 84 | err := build.SetKey(starlark.String("author"), starlark.String("octocat")) 85 | if err != nil { 86 | t.Error(err) 87 | } 88 | 89 | repo := starlark.NewDict(1) 90 | err = repo.SetKey(starlark.String("full_name"), starlark.String("go-vela/hello-world")) 91 | if err != nil { 92 | t.Error(err) 93 | } 94 | 95 | user := starlark.NewDict(1) 96 | err = user.SetKey(starlark.String("admin"), starlark.String("true")) 97 | if err != nil { 98 | t.Error(err) 99 | } 100 | 101 | system := starlark.NewDict(2) 102 | err = system.SetKey(starlark.String("template_name"), starlark.String("foo")) 103 | if err != nil { 104 | t.Error(err) 105 | } 106 | err = system.SetKey(starlark.String("workspace"), starlark.String("/vela/src/github.com/go-vela/hello-world")) 107 | if err != nil { 108 | t.Error(err) 109 | } 110 | 111 | withAllPre := starlark.NewDict(0) 112 | err = withAllPre.SetKey(starlark.String("build"), build) 113 | if err != nil { 114 | t.Error(err) 115 | } 116 | err = withAllPre.SetKey(starlark.String("repo"), repo) 117 | if err != nil { 118 | t.Error(err) 119 | } 120 | err = withAllPre.SetKey(starlark.String("user"), user) 121 | if err != nil { 122 | t.Error(err) 123 | } 124 | err = withAllPre.SetKey(starlark.String("system"), system) 125 | if err != nil { 126 | t.Error(err) 127 | } 128 | 129 | tests := []struct { 130 | name string 131 | slice raw.StringSliceMap 132 | templateName string 133 | want *starlark.Dict 134 | wantErr bool 135 | }{ 136 | { 137 | name: "with all vela prefixed var", 138 | slice: raw.StringSliceMap{ 139 | "VELA_BUILD_AUTHOR": "octocat", 140 | "VELA_REPO_FULL_NAME": "go-vela/hello-world", 141 | "VELA_USER_ADMIN": "true", 142 | "VELA_WORKSPACE": "/vela/src/github.com/go-vela/hello-world", 143 | }, 144 | templateName: "foo", 145 | want: withAllPre, 146 | }, 147 | } 148 | for _, tt := range tests { 149 | t.Run(tt.name, func(t *testing.T) { 150 | got, err := convertPlatformVars(tt.slice, tt.templateName) 151 | if (err != nil) != tt.wantErr { 152 | t.Errorf("convertPlatformVars() error = %v, wantErr %v", err, tt.wantErr) 153 | return 154 | } 155 | 156 | if !reflect.DeepEqual(got, tt.want) { 157 | t.Errorf("convertPlatformVars() = %v, want %v", got, tt.want) 158 | } 159 | }) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /template/starlark/render.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package starlark 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "fmt" 11 | 12 | "github.com/go-vela/types/raw" 13 | 14 | yaml "github.com/buildkite/yaml" 15 | types "github.com/go-vela/types/yaml" 16 | "go.starlark.net/starlark" 17 | ) 18 | 19 | var ( 20 | // ErrMissingMainFunc defines the error type when the 21 | // main function does not exist in the provided template. 22 | ErrMissingMainFunc = errors.New("unable to find main function in template") 23 | 24 | // ErrInvalidMainFunc defines the error type when the 25 | // main function is invalid within the provided template. 26 | ErrInvalidMainFunc = errors.New("invalid main function (main must be a function) in template") 27 | 28 | // ErrInvalidPipelineReturn defines the error type when the 29 | // return type is not a pipeline within the provided template. 30 | ErrInvalidPipelineReturn = errors.New("invalid pipeline return in template") 31 | ) 32 | 33 | // RenderStep combines the template with the step in the yaml pipeline. 34 | // 35 | // nolint: funlen,lll // ignore function length due to comments 36 | func RenderStep(tmpl string, s *types.Step) (types.StepSlice, types.SecretSlice, types.ServiceSlice, raw.StringSliceMap, error) { 37 | config := new(types.Build) 38 | 39 | thread := &starlark.Thread{Name: s.Name} 40 | // arbitrarily limiting the steps of the thread to 5000 to help prevent infinite loops 41 | // may need to further investigate spawning a separate POSIX process if user input is problematic 42 | // see https://github.com/google/starlark-go/issues/160#issuecomment-466794230 for further details 43 | // 44 | // nolint: gomnd // ignore magic number 45 | thread.SetMaxExecutionSteps(5000) 46 | globals, err := starlark.ExecFile(thread, s.Template.Name, tmpl, nil) 47 | if err != nil { 48 | return nil, nil, nil, nil, err 49 | } 50 | 51 | // check the provided template has a main function 52 | mainVal, ok := globals["main"] 53 | if !ok { 54 | return nil, nil, nil, nil, fmt.Errorf("%s: %s", ErrMissingMainFunc, s.Template.Name) 55 | } 56 | 57 | // check the provided main is a function 58 | main, ok := mainVal.(starlark.Callable) 59 | if !ok { 60 | return nil, nil, nil, nil, fmt.Errorf("%s: %s", ErrInvalidMainFunc, s.Template.Name) 61 | } 62 | 63 | // load the user provided vars into a starlark type 64 | userVars, err := convertTemplateVars(s.Template.Variables) 65 | if err != nil { 66 | return nil, nil, nil, nil, err 67 | } 68 | 69 | // load the platform provided vars into a starlark type 70 | velaVars, err := convertPlatformVars(s.Environment, s.Name) 71 | if err != nil { 72 | return nil, nil, nil, nil, err 73 | } 74 | 75 | // add the user and platform vars to a context to be used 76 | // within the template caller i.e. ctx["vela"] or ctx["vars"] 77 | context := starlark.NewDict(0) 78 | err = context.SetKey(starlark.String("vela"), velaVars) 79 | if err != nil { 80 | return nil, nil, nil, nil, err 81 | } 82 | err = context.SetKey(starlark.String("vars"), userVars) 83 | if err != nil { 84 | return nil, nil, nil, nil, err 85 | } 86 | 87 | args := starlark.Tuple([]starlark.Value{context}) 88 | 89 | // execute Starlark program from Go. 90 | mainVal, err = starlark.Call(thread, main, args, nil) 91 | if err != nil { 92 | return nil, nil, nil, nil, err 93 | } 94 | 95 | buf := new(bytes.Buffer) 96 | 97 | // extract the pipeline from the starlark program 98 | switch v := mainVal.(type) { 99 | case *starlark.List: 100 | for i := 0; i < v.Len(); i++ { 101 | item := v.Index(i) 102 | buf.WriteString("---\n") 103 | err = writeJSON(buf, item) 104 | if err != nil { 105 | return nil, nil, nil, nil, err 106 | } 107 | buf.WriteString("\n") 108 | } 109 | case *starlark.Dict: 110 | buf.WriteString("---\n") 111 | err = writeJSON(buf, v) 112 | if err != nil { 113 | return nil, nil, nil, nil, err 114 | } 115 | default: 116 | return nil, nil, nil, nil, fmt.Errorf("%s: %s", ErrInvalidPipelineReturn, mainVal.Type()) 117 | } 118 | 119 | // unmarshal the template to the pipeline 120 | err = yaml.Unmarshal(buf.Bytes(), config) 121 | if err != nil { 122 | // nolint: lll // ignore long line length due to return args 123 | return types.StepSlice{}, types.SecretSlice{}, types.ServiceSlice{}, raw.StringSliceMap{}, fmt.Errorf("unable to unmarshal yaml: %v", err) 124 | } 125 | 126 | // ensure all templated steps have template prefix 127 | for index, newStep := range config.Steps { 128 | config.Steps[index].Name = fmt.Sprintf("%s_%s", s.Name, newStep.Name) 129 | } 130 | 131 | return config.Steps, config.Secrets, config.Services, config.Environment, nil 132 | } 133 | 134 | // RenderBuild renders the templated build. 135 | func RenderBuild(b string, envs map[string]string) (*types.Build, error) { 136 | config := new(types.Build) 137 | 138 | thread := &starlark.Thread{Name: "templated-base"} 139 | // arbitrarily limiting the steps of the thread to 5000 to help prevent infinite loops 140 | // may need to further investigate spawning a separate POSIX process if user input is problematic 141 | // see https://github.com/google/starlark-go/issues/160#issuecomment-466794230 for further details 142 | // 143 | // nolint: gomnd // ignore magic number 144 | thread.SetMaxExecutionSteps(5000) 145 | globals, err := starlark.ExecFile(thread, "templated-base", b, nil) 146 | if err != nil { 147 | return nil, err 148 | } 149 | 150 | // check the provided template has a main function 151 | mainVal, ok := globals["main"] 152 | if !ok { 153 | return nil, fmt.Errorf("%s: %s", ErrMissingMainFunc, "templated-base") 154 | } 155 | 156 | // check the provided main is a function 157 | main, ok := mainVal.(starlark.Callable) 158 | if !ok { 159 | return nil, fmt.Errorf("%s: %s", ErrInvalidMainFunc, "templated-base") 160 | } 161 | 162 | // load the platform provided vars into a starlark type 163 | velaVars, err := convertPlatformVars(envs, "") 164 | if err != nil { 165 | return nil, err 166 | } 167 | 168 | // add the user and platform vars to a context to be used 169 | // within the template caller i.e. ctx["vela"] or ctx["vars"] 170 | context := starlark.NewDict(0) 171 | err = context.SetKey(starlark.String("vela"), velaVars) 172 | if err != nil { 173 | return nil, err 174 | } 175 | 176 | args := starlark.Tuple([]starlark.Value{context}) 177 | 178 | // execute Starlark program from Go. 179 | mainVal, err = starlark.Call(thread, main, args, nil) 180 | if err != nil { 181 | return nil, err 182 | } 183 | 184 | buf := new(bytes.Buffer) 185 | 186 | // extract the pipeline from the starlark program 187 | switch v := mainVal.(type) { 188 | case *starlark.List: 189 | for i := 0; i < v.Len(); i++ { 190 | item := v.Index(i) 191 | buf.WriteString("---\n") 192 | err = writeJSON(buf, item) 193 | if err != nil { 194 | return nil, err 195 | } 196 | buf.WriteString("\n") 197 | } 198 | case *starlark.Dict: 199 | buf.WriteString("---\n") 200 | err = writeJSON(buf, v) 201 | if err != nil { 202 | return nil, err 203 | } 204 | default: 205 | return nil, fmt.Errorf("%s: %s", ErrInvalidPipelineReturn, mainVal.Type()) 206 | } 207 | 208 | // unmarshal the template to the pipeline 209 | err = yaml.Unmarshal(buf.Bytes(), config) 210 | if err != nil { 211 | return nil, fmt.Errorf("unable to unmarshal yaml: %v", err) 212 | } 213 | 214 | return config, nil 215 | } 216 | -------------------------------------------------------------------------------- /template/starlark/render_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package starlark 6 | 7 | import ( 8 | "io/ioutil" 9 | "testing" 10 | 11 | goyaml "github.com/buildkite/yaml" 12 | "github.com/go-vela/types/raw" 13 | "github.com/go-vela/types/yaml" 14 | "github.com/google/go-cmp/cmp" 15 | ) 16 | 17 | func TestStarlark_RenderStep(t *testing.T) { 18 | type args struct { 19 | velaFile string 20 | starlarkFile string 21 | } 22 | tests := []struct { 23 | name string 24 | args args 25 | wantFile string 26 | wantErr bool 27 | }{ 28 | {"basic", args{velaFile: "testdata/step/basic/step.yml", starlarkFile: "testdata/step/basic/template.py"}, "testdata/step/basic/want.yml", false}, 29 | {"with method", args{velaFile: "testdata/step/with_method/step.yml", starlarkFile: "testdata/step/with_method/template.star"}, "testdata/step/with_method/want.yml", false}, 30 | {"user vars", args{velaFile: "testdata/step/with_vars/step.yml", starlarkFile: "testdata/step/with_vars/template.star"}, "testdata/step/with_vars/want.yml", false}, 31 | {"platform vars", args{velaFile: "testdata/step/with_vars_plat/step.yml", starlarkFile: "testdata/step/with_vars_plat/template.star"}, "testdata/step/with_vars_plat/want.yml", false}, 32 | {"cancel due to complexity", args{velaFile: "testdata/step/cancel/step.yml", starlarkFile: "testdata/step/cancel/template.star"}, "", true}, 33 | } 34 | for _, tt := range tests { 35 | t.Run(tt.name, func(t *testing.T) { 36 | sFile, err := ioutil.ReadFile(tt.args.velaFile) 37 | if err != nil { 38 | t.Error(err) 39 | } 40 | b := &yaml.Build{} 41 | err = goyaml.Unmarshal(sFile, b) 42 | if err != nil { 43 | t.Error(err) 44 | } 45 | b.Steps[0].Environment = raw.StringSliceMap{ 46 | "VELA_REPO_FULL_NAME": "octocat/hello-world", 47 | } 48 | 49 | tmpl, err := ioutil.ReadFile(tt.args.starlarkFile) 50 | if err != nil { 51 | t.Error(err) 52 | } 53 | 54 | steps, secrets, services, environment, err := RenderStep(string(tmpl), b.Steps[0]) 55 | if (err != nil) != tt.wantErr { 56 | t.Errorf("RenderStep() error = %v, wantErr %v", err, tt.wantErr) 57 | return 58 | } 59 | 60 | if tt.wantErr != true { 61 | wFile, err := ioutil.ReadFile(tt.wantFile) 62 | if err != nil { 63 | t.Error(err) 64 | } 65 | w := &yaml.Build{} 66 | err = goyaml.Unmarshal(wFile, w) 67 | if err != nil { 68 | t.Error(err) 69 | } 70 | wantSteps := w.Steps 71 | wantSecrets := w.Secrets 72 | wantServices := w.Services 73 | wantEnvironment := w.Environment 74 | 75 | if diff := cmp.Diff(wantSteps, steps); diff != "" { 76 | t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff) 77 | } 78 | if diff := cmp.Diff(wantSecrets, secrets); diff != "" { 79 | t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff) 80 | } 81 | if diff := cmp.Diff(wantServices, services); diff != "" { 82 | t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff) 83 | } 84 | if diff := cmp.Diff(wantEnvironment, environment); diff != "" { 85 | t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff) 86 | } 87 | } 88 | }) 89 | } 90 | } 91 | 92 | func TestNative_RenderBuild(t *testing.T) { 93 | type args struct { 94 | velaFile string 95 | } 96 | tests := []struct { 97 | name string 98 | args args 99 | wantFile string 100 | wantErr bool 101 | }{ 102 | {"steps", args{velaFile: "testdata/build/basic/build.star"}, "testdata/build/basic/want.yml", false}, 103 | {"stages", args{velaFile: "testdata/build/basic_stages/build.star"}, "testdata/build/basic_stages/want.yml", false}, 104 | {"conditional match", args{velaFile: "testdata/build/conditional/build.star"}, "testdata/build/conditional/want.yml", false}, 105 | } 106 | for _, tt := range tests { 107 | t.Run(tt.name, func(t *testing.T) { 108 | sFile, err := ioutil.ReadFile(tt.args.velaFile) 109 | if err != nil { 110 | t.Error(err) 111 | } 112 | 113 | got, err := RenderBuild(string(sFile), map[string]string{ 114 | "VELA_REPO_FULL_NAME": "octocat/hello-world", 115 | "VELA_BUILD_BRANCH": "master", 116 | }) 117 | if (err != nil) != tt.wantErr { 118 | t.Errorf("RenderBuild() error = %v, wantErr %v", err, tt.wantErr) 119 | return 120 | } 121 | 122 | if tt.wantErr != true { 123 | wFile, err := ioutil.ReadFile(tt.wantFile) 124 | if err != nil { 125 | t.Error(err) 126 | } 127 | want := &yaml.Build{} 128 | err = goyaml.Unmarshal(wFile, want) 129 | if err != nil { 130 | t.Error(err) 131 | } 132 | 133 | if diff := cmp.Diff(want, got); diff != "" { 134 | t.Errorf("RenderBuild() mismatch (-want +got):\n%s", diff) 135 | } 136 | } 137 | }) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /template/starlark/starlark.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package starlark 6 | 7 | import ( 8 | "bytes" 9 | "encoding/json" 10 | "errors" 11 | "fmt" 12 | "reflect" 13 | 14 | "github.com/sirupsen/logrus" 15 | "go.starlark.net/starlark" 16 | "k8s.io/apimachinery/pkg/util/intstr" 17 | ) 18 | 19 | var ( 20 | // ErrUnableToConvertStarlark defines the error type when the 21 | // toStarlark cannot convert the provided value. 22 | ErrUnableToConvertStarlark = errors.New("unable to convert to starlark type") 23 | 24 | // ErrUnableToConvertJSON defines the error type when the 25 | // writeJSON cannot convert the provided value. 26 | ErrUnableToConvertJSON = errors.New("unable to convert to json") 27 | ) 28 | 29 | // toStarlark takes an value as an interface an 30 | // will return the comparable Starlark type. 31 | // 32 | // This code is under copyright (full attribution in NOTICE) and is from: 33 | // 34 | // https://github.com/wonderix/shalm/blob/899b8f7787883d40619eefcc39bd12f42a09b5e7/pkg/shalm/convert.go#L14-L85 35 | // 36 | // nolint: gocyclo,funlen,lll // ignore above line length and function length due to comments 37 | func toStarlark(value interface{}) (starlark.Value, error) { 38 | logrus.Tracef("converting %v to starlark type", value) 39 | 40 | if value == nil { 41 | return starlark.None, nil 42 | } 43 | 44 | switch v := reflect.ValueOf(value); v.Kind() { 45 | case reflect.String: 46 | return starlark.String(v.String()), nil 47 | case reflect.Bool: 48 | return starlark.Bool(v.Bool()), nil 49 | case reflect.Int: 50 | fallthrough 51 | case reflect.Int32: 52 | fallthrough 53 | case reflect.Int64: 54 | fallthrough 55 | case reflect.Int16: 56 | return starlark.MakeInt64(v.Int()), nil 57 | case reflect.Uint: 58 | fallthrough 59 | case reflect.Uint32: 60 | fallthrough 61 | case reflect.Uint64: 62 | fallthrough 63 | case reflect.Uint16: 64 | return starlark.MakeUint64(v.Uint()), nil 65 | case reflect.Float32: 66 | return starlark.Float(v.Float()), nil 67 | case reflect.Float64: 68 | return starlark.Float(v.Float()), nil 69 | case reflect.Slice: 70 | if b, ok := value.([]byte); ok { 71 | return starlark.String(string(b)), nil 72 | } 73 | 74 | a := make([]starlark.Value, 0) 75 | 76 | for i := 0; i < v.Len(); i++ { 77 | val, err := toStarlark(v.Index(i).Interface()) 78 | if err != nil { 79 | return nil, err 80 | } 81 | a = append(a, val) 82 | } 83 | 84 | return starlark.Tuple(a), nil 85 | case reflect.Ptr: 86 | val, err := toStarlark(v.Elem().Interface()) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | return val, nil 92 | case reflect.Map: 93 | // nolint: gomnd // ignore magic number 94 | d := starlark.NewDict(16) 95 | 96 | for _, key := range v.MapKeys() { 97 | strct := v.MapIndex(key) 98 | keyValue, err := toStarlark(key.Interface()) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | kv, err := toStarlark(strct.Interface()) 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | err = d.SetKey(keyValue, kv) 109 | if err != nil { 110 | return nil, err 111 | } 112 | } 113 | 114 | return d, nil 115 | case reflect.Struct: 116 | ios, ok := value.(intstr.IntOrString) 117 | if ok { 118 | switch ios.Type { 119 | case intstr.String: 120 | return starlark.String(ios.StrVal), nil 121 | case intstr.Int: 122 | return starlark.MakeInt(int(ios.IntVal)), nil 123 | } 124 | } else { 125 | data, err := json.Marshal(value) 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | var m map[string]interface{} 131 | err = json.Unmarshal(data, &m) 132 | if err != nil { 133 | return nil, err 134 | } 135 | 136 | return toStarlark(m) 137 | } 138 | } 139 | 140 | return nil, fmt.Errorf("%s: %v", ErrUnableToConvertStarlark, value) 141 | } 142 | 143 | // writeJSON takes an starlark input and return the valid JSON 144 | // for the specific type. 145 | // 146 | // This code is under copyright (full attribution in NOTICE) and is from: 147 | // 148 | // https://github.com/drone/drone-cli/blob/master/drone/starlark/starlark.go#L214-L274 149 | // 150 | // Note: we are using logrus log unchecked errors that the original implementation ignored. 151 | // if/when we try to return values it breaks the recursion. Panics were swapped to error 152 | // returns from implementation. 153 | // 154 | // nolint: gocyclo,funlen // ignore cyclomatic complexity and function length 155 | func writeJSON(out *bytes.Buffer, v starlark.Value) error { 156 | logrus.Tracef("converting %v to JSON", v) 157 | 158 | if marshaler, ok := v.(json.Marshaler); ok { 159 | jsonData, err := marshaler.MarshalJSON() 160 | if err != nil { 161 | return err 162 | } 163 | 164 | _, err = out.Write(jsonData) 165 | if err != nil { 166 | logrus.Error(err) 167 | } 168 | 169 | return nil 170 | } 171 | 172 | switch v := v.(type) { 173 | case starlark.NoneType: 174 | _, err := out.WriteString("null") 175 | if err != nil { 176 | logrus.Error(err) 177 | } 178 | case starlark.Bool: 179 | _, err := fmt.Fprintf(out, "%t", v) 180 | if err != nil { 181 | logrus.Error(err) 182 | } 183 | case starlark.Int: 184 | _, err := out.WriteString(v.String()) 185 | if err != nil { 186 | logrus.Error(err) 187 | } 188 | case starlark.Float: 189 | _, err := fmt.Fprintf(out, "%g", v) 190 | if err != nil { 191 | logrus.Error(err) 192 | } 193 | case starlark.String: 194 | s := string(v) 195 | 196 | if goQuoteIsSafe(s) { 197 | fmt.Fprintf(out, "%q", s) 198 | } else { 199 | // vanishingly rare for text strings 200 | data, err := json.Marshal(s) 201 | if err != nil { 202 | logrus.Error(err) 203 | } 204 | 205 | _, err = out.Write(data) 206 | if err != nil { 207 | logrus.Error(err) 208 | } 209 | } 210 | case starlark.Indexable: // Tuple, List 211 | err := out.WriteByte('[') 212 | if err != nil { 213 | logrus.Error(err) 214 | } 215 | 216 | for i, n := 0, starlark.Len(v); i < n; i++ { 217 | if i > 0 { 218 | _, err := out.WriteString(", ") 219 | if err != nil { 220 | logrus.Error(err) 221 | } 222 | } 223 | 224 | err := writeJSON(out, v.Index(i)) 225 | if err != nil { 226 | return err 227 | } 228 | } 229 | 230 | err = out.WriteByte(']') 231 | if err != nil { 232 | logrus.Error(err) 233 | } 234 | case *starlark.Dict: 235 | err := out.WriteByte('{') 236 | if err != nil { 237 | logrus.Error(err) 238 | } 239 | 240 | for i, itemPair := range v.Items() { 241 | key, value := itemPair[0], itemPair[1] 242 | 243 | if i > 0 { 244 | _, err := out.WriteString(", ") 245 | if err != nil { 246 | logrus.Error(err) 247 | } 248 | } 249 | 250 | err := writeJSON(out, key) 251 | if err != nil { 252 | return err 253 | } 254 | 255 | _, err = out.WriteString(": ") 256 | if err != nil { 257 | logrus.Error(err) 258 | } 259 | 260 | err = writeJSON(out, value) 261 | if err != nil { 262 | return err 263 | } 264 | } 265 | 266 | err = out.WriteByte('}') 267 | if err != nil { 268 | logrus.Error(err) 269 | } 270 | default: 271 | return fmt.Errorf("%s: %v", ErrUnableToConvertJSON, v) 272 | } 273 | 274 | return nil 275 | } 276 | 277 | // goQuoteIsSafe takes a string and checks if is safely quoted 278 | // 279 | // This code is under copyright (full attribution in NOTICE) and is from: 280 | // https://github.com/drone/drone-cli/blob/master/drone/starlark/starlark.go#L276-L285 281 | func goQuoteIsSafe(s string) bool { 282 | for _, r := range s { 283 | // JSON doesn't like Go's \xHH escapes for ASCII control codes, 284 | // nor its \UHHHHHHHH escapes for runes >16 bits. 285 | if r < 0x20 || r >= 0x10000 { 286 | return false 287 | } 288 | } 289 | return true 290 | } 291 | -------------------------------------------------------------------------------- /template/starlark/starlark_test.go: -------------------------------------------------------------------------------- 1 | package starlark 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "go.starlark.net/starlark" 8 | ) 9 | 10 | func TestStarlark_toStarlark(t *testing.T) { 11 | dict := starlark.NewDict(16) 12 | err := dict.SetKey(starlark.String("foo"), starlark.String("bar")) 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | a := make([]starlark.Value, 0) 17 | a = append(a, starlark.Value(starlark.String("foo"))) 18 | a = append(a, starlark.Value(starlark.String("bar"))) 19 | type args struct { 20 | value interface{} 21 | } 22 | tests := []struct { 23 | name string 24 | args args 25 | want starlark.Value 26 | wantErr bool 27 | }{ 28 | {"string", args{value: "foo"}, starlark.String("foo"), false}, 29 | {"byte array", args{value: []byte("array")}, starlark.String("array"), false}, 30 | {"array", args{value: []string{"foo", "bar"}}, starlark.Tuple(a), false}, 31 | {"bool", args{value: true}, starlark.Bool(true), false}, 32 | {"float64", args{value: 0.1}, starlark.Float(0.1), false}, 33 | {"float32", args{value: float32(0.1)}, starlark.Float(float32(0.1)), false}, 34 | {"int", args{value: 1}, starlark.MakeInt(1), false}, 35 | {"int32", args{value: int32(1)}, starlark.MakeInt(1), false}, 36 | {"int64", args{value: int64(1)}, starlark.MakeInt(1), false}, 37 | {"int16", args{value: int16(1)}, starlark.MakeInt(1), false}, 38 | {"unit", args{value: uint(1)}, starlark.MakeInt(1), false}, 39 | {"unit32", args{value: uint32(1)}, starlark.MakeInt(1), false}, 40 | {"unit64", args{value: uint64(1)}, starlark.MakeInt(1), false}, 41 | {"unit16", args{value: uint16(1)}, starlark.MakeInt(1), false}, 42 | {"nil", args{value: nil}, starlark.None, false}, 43 | {"map", args{value: map[string]string{"foo": "bar"}}, dict, false}, 44 | } 45 | for _, tt := range tests { 46 | t.Run(tt.name, func(t *testing.T) { 47 | got, err := toStarlark(tt.args.value) 48 | if (err != nil) != tt.wantErr { 49 | t.Errorf("toStarlark() error = %v, wantErr %v", err, tt.wantErr) 50 | return 51 | } 52 | if !reflect.DeepEqual(got, tt.want) { 53 | t.Errorf("toStarlark() got = %v, want %v", got, tt.want) 54 | } 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /template/starlark/testdata/build/basic/build.star: -------------------------------------------------------------------------------- 1 | def main(ctx): 2 | stepNames = ["foo", "bar", "star"] 3 | 4 | steps = [] 5 | 6 | for name in stepNames: 7 | steps.append(step(name)) 8 | 9 | return { 10 | 'version': '1', 11 | 'steps': steps 12 | } 13 | 14 | def step(word): 15 | return { 16 | "name": "build_%s" % word, 17 | "image": "alpine:latest", 18 | 'commands': [ 19 | "echo %s" % word 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /template/starlark/testdata/build/basic/want.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | steps: 3 | - name: build_foo 4 | image: alpine:latest 5 | commands: 6 | - echo foo 7 | 8 | - name: build_bar 9 | image: alpine:latest 10 | commands: 11 | - echo bar 12 | 13 | - name: build_star 14 | image: alpine:latest 15 | commands: 16 | - echo star 17 | -------------------------------------------------------------------------------- /template/starlark/testdata/build/basic_stages/build.star: -------------------------------------------------------------------------------- 1 | def main(ctx): 2 | stageNames = ["foo", "bar", "star"] 3 | 4 | stages = {} 5 | 6 | for name in stageNames: 7 | stages[name] = stage(name) 8 | 9 | return { 10 | 'version': '1', 11 | 'stages': stages 12 | } 13 | 14 | def stage(word): 15 | return { 16 | "steps": [ 17 | { 18 | "name": "build_%s" % word, 19 | "image": "alpine:latest", 20 | 'commands': [ 21 | "echo hello from %s" % word 22 | ] 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /template/starlark/testdata/build/basic_stages/want.yml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | 3 | stages: 4 | foo: 5 | steps: 6 | - name: build_foo 7 | image: alpine:latest 8 | commands: 9 | - echo hello from foo 10 | needs: [ "clone" ] 11 | pull: not_present 12 | 13 | bar: 14 | steps: 15 | - name: build_bar 16 | image: alpine:latest 17 | commands: 18 | - echo hello from bar 19 | needs: [ "clone" ] 20 | pull: not_present 21 | 22 | star: 23 | steps: 24 | - name: build_star 25 | image: alpine:latest 26 | commands: 27 | - echo hello from star 28 | needs: [ "clone" ] 29 | pull: not_present 30 | 31 | -------------------------------------------------------------------------------- /template/starlark/testdata/build/conditional/build.star: -------------------------------------------------------------------------------- 1 | def main(ctx): 2 | stepNames = ["foo", "bar", "star"] 3 | 4 | steps = [] 5 | 6 | for name in stepNames: 7 | if name == "foo" and ctx["vela"]["build"]["branch"] == "master": 8 | steps.append(step(name)) 9 | 10 | return { 11 | 'version': '1', 12 | 'steps': steps 13 | } 14 | 15 | def step(word): 16 | return { 17 | "name": "build_%s" % word, 18 | "image": "alpine:latest", 19 | 'commands': [ 20 | "echo %s" % word 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /template/starlark/testdata/build/conditional/want.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | steps: 3 | - name: build_foo 4 | image: alpine:latest 5 | commands: 6 | - echo foo 7 | -------------------------------------------------------------------------------- /template/starlark/testdata/step/basic/step.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample 3 | template: 4 | name: golang 5 | vars: 6 | image: golang:latest 7 | pull_policy: "pull: true" -------------------------------------------------------------------------------- /template/starlark/testdata/step/basic/template.py: -------------------------------------------------------------------------------- 1 | def main(ctx): 2 | return { 3 | 'version': '1', 4 | 'steps': [ 5 | { 6 | 'name': 'build', 7 | 'image': 'golang:latest', 8 | 'commands': [ 9 | 'go build', 10 | 'go test', 11 | ] 12 | }, 13 | ], 14 | } -------------------------------------------------------------------------------- /template/starlark/testdata/step/basic/want.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | steps: 3 | - name: sample_build 4 | image: golang:latest 5 | commands: 6 | - go build 7 | - go test -------------------------------------------------------------------------------- /template/starlark/testdata/step/cancel/step.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample 3 | template: 4 | name: echo 5 | vars: 6 | image: golang:latest 7 | pull_policy: "pull: true" -------------------------------------------------------------------------------- /template/starlark/testdata/step/cancel/template.star: -------------------------------------------------------------------------------- 1 | def main(ctx): 2 | foo = ''; 3 | for i in range(1, 100000): 4 | foo = foo 5 | -------------------------------------------------------------------------------- /template/starlark/testdata/step/with_method/step.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample 3 | template: 4 | name: echo 5 | vars: 6 | image: golang:latest 7 | pull_policy: "pull: true" -------------------------------------------------------------------------------- /template/starlark/testdata/step/with_method/template.star: -------------------------------------------------------------------------------- 1 | def main(ctx): 2 | return { 3 | 'version': '1', 4 | 'steps': [ 5 | step('foo'), 6 | step('bar') 7 | ], 8 | } 9 | 10 | def step(word): 11 | return { 12 | "name": "build_%s" % word, 13 | "image": "alpine:latest", 14 | 'commands': [ 15 | "echo %s" % word 16 | ] 17 | } -------------------------------------------------------------------------------- /template/starlark/testdata/step/with_method/want.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | steps: 3 | - name: sample_build_foo 4 | image: alpine:latest 5 | commands: 6 | - echo foo 7 | 8 | - name: sample_build_bar 9 | image: alpine:latest 10 | commands: 11 | - echo bar -------------------------------------------------------------------------------- /template/starlark/testdata/step/with_vars/step.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample 3 | template: 4 | name: build 5 | vars: 6 | tags: [latest, "1.14", "1.15"] 7 | pull_policy: always 8 | commands: 9 | test: "go test ./..." 10 | build: "go test ./..." -------------------------------------------------------------------------------- /template/starlark/testdata/step/with_vars/template.star: -------------------------------------------------------------------------------- 1 | def main(ctx): 2 | steps = [step(x, ctx["vars"]["pull_policy"], ctx["vars"]["commands"]) for x in ctx["vars"]["tags"]] 3 | 4 | pipeline = { 5 | 'version': '1', 6 | 'steps': steps, 7 | } 8 | 9 | return pipeline 10 | 11 | def step(tag, pull_policy, commands): 12 | return { 13 | "name": "build %s" % tag, 14 | "image": "golang:%s" % tag, 15 | "pull": pull_policy, 16 | 'commands': commands.values(), 17 | } 18 | -------------------------------------------------------------------------------- /template/starlark/testdata/step/with_vars/want.yml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | steps: 3 | - name: sample_build latest 4 | image: golang:latest 5 | pull: always 6 | commands: 7 | - go test ./... 8 | - go test ./... 9 | 10 | - name: sample_build 1.14 11 | image: golang:1.14 12 | pull: always 13 | commands: 14 | - go test ./... 15 | - go test ./... 16 | 17 | - name: sample_build 1.15 18 | image: golang:1.15 19 | pull: always 20 | commands: 21 | - go test ./... 22 | - go test ./... -------------------------------------------------------------------------------- /template/starlark/testdata/step/with_vars_plat/step.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: sample 3 | template: 4 | name: echo 5 | vars: 6 | -------------------------------------------------------------------------------- /template/starlark/testdata/step/with_vars_plat/template.star: -------------------------------------------------------------------------------- 1 | def main(ctx): 2 | return { 3 | 'version': '1', 4 | 'steps': [ 5 | step(ctx["vela"]["repo"]["full_name"]), 6 | ], 7 | } 8 | 9 | def step(full_name): 10 | return { 11 | "name": "echo %s" % full_name, 12 | "image": "alpine:latest", 13 | 'commands': [ 14 | "echo %s" % full_name 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /template/starlark/testdata/step/with_vars_plat/want.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | steps: 3 | - name: sample_echo octocat/hello-world 4 | image: alpine:latest 5 | commands: 6 | - echo octocat/hello-world -------------------------------------------------------------------------------- /template/template.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Target Brands, Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by the LICENSE file in this repository. 4 | 5 | package template 6 | 7 | import "github.com/go-vela/types/yaml" 8 | 9 | // Engine represents the interface for Vela integrating 10 | // with the different supported template engines. 11 | type Engine interface { 12 | // RenderBuild defines a function that combines 13 | // the template with the build. 14 | RenderBuild(template string, step *yaml.Step) (yaml.StepSlice, error) 15 | // RenderStep defines a function that combines 16 | // the template with the step. 17 | RenderStep(template string, step *yaml.Step) (yaml.StepSlice, error) 18 | } 19 | --------------------------------------------------------------------------------