diff --git a/.cliff.toml b/.cliff.toml new file mode 100644 index 0000000..702629f --- /dev/null +++ b/.cliff.toml @@ -0,0 +1,181 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration + +[changelog] +header = """ +""" + +footer = """ + +----- + +**[{{ remote.github.repo }}]({{ self::remote_url() }}) license terms** + +[![License][license-badge]][license-url] + +[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg +[license-url]: {{ self::remote_url() }}/?tab=Apache-2.0-1-ov-file#readme + +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} +""" + +body = """ +{%- if version %} +## [{{ version | trim_start_matches(pat="v") }}]({{ self::remote_url() }}/tree/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }} +{%- else %} +## [unreleased] +{%- endif %} +{%- if message %} + {%- raw %}\n{% endraw %} +{{ message }} + {%- raw %}\n{% endraw %} +{%- endif %} +{%- if version %} + {%- if previous.version %} + +**Full Changelog**: <{{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}> + {%- endif %} +{%- else %} + {%- raw %}\n{% endraw %} +{%- endif %} + +{%- if statistics %}{% if statistics.commit_count %} + {%- raw %}\n{% endraw %} +{{ statistics.commit_count }} commits in this release. + {%- raw %}\n{% endraw %} +{%- endif %}{% endif %} +----- + +{%- for group, commits in commits | group_by(attribute="group") %} + {%- raw %}\n{% endraw %} +### {{ group | upper_first }} + {%- raw %}\n{% endraw %} + {%- for commit in commits %} + {%- if commit.remote.pr_title %} + {%- set commit_message = commit.remote.pr_title %} + {%- else %} + {%- set commit_message = commit.message %} + {%- endif %} +* {{ commit_message | split(pat="\n") | first | trim }} + {%- if commit.remote.username %} +{%- raw %} {% endraw %}by [@{{ commit.remote.username }}](https://github.com/{{ commit.remote.username }}) + {%- endif %} + {%- if commit.remote.pr_number %} +{%- raw %} {% endraw %}in [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) + {%- endif %} +{%- raw %} {% endraw %}[...]({{ self::remote_url() }}/commit/{{ commit.id }}) + {%- endfor %} +{%- endfor %} + +{%- if github %} +{%- raw %}\n{% endraw -%} + {%- set all_contributors = github.contributors | length %} + {%- if github.contributors | filter(attribute="username", value="dependabot[bot]") | length < all_contributors %} +----- + +### People who contributed to this release + {% endif %} + {%- for contributor in github.contributors | filter(attribute="username") | sort(attribute="username") %} + {%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %} +* [@{{ contributor.username }}](https://github.com/{{ contributor.username }}) + {%- endif %} + {%- endfor %} + + {% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} +----- + {%- raw %}\n{% endraw %} + +### New Contributors + {%- endif %} + + {%- for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} + {%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %} +* @{{ contributor.username }} made their first contribution + {%- if contributor.pr_number %} + in [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \ + {%- endif %} + {%- endif %} + {%- endfor %} +{%- endif %} + +{%- raw %}\n{% endraw %} + +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} +""" +# Remove leading and trailing whitespaces from the changelog's body. +trim = true +# Render body even when there are no releases to process. +render_always = true +# An array of regex based postprocessors to modify the changelog. +postprocessors = [ + # Replace the placeholder with a URL. + #{ pattern = '', replace = "https://github.com/orhun/git-cliff" }, +] +# output file path +# output = "test.md" + +[git] +# Parse commits according to the conventional commits specification. +# See https://www.conventionalcommits.org +conventional_commits = false +# Exclude commits that do not match the conventional commits specification. +filter_unconventional = false +# Require all commits to be conventional. +# Takes precedence over filter_unconventional. +require_conventional = false +# Split commits on newlines, treating each line as an individual commit. +split_commits = false +# An array of regex based parsers to modify commit messages prior to further processing. +commit_preprocessors = [ + # Replace issue numbers with link templates to be updated in `changelog.postprocessors`. + #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, + # Check spelling of the commit message using https://github.com/crate-ci/typos. + # If the spelling is incorrect, it will be fixed automatically. + #{ pattern = '.*', replace_command = 'typos --write-changes -' } +] +# Prevent commits that are breaking from being excluded by commit parsers. +protect_breaking_commits = false +# An array of regex based parsers for extracting data from the commit message. +# Assigns commits to groups. +# Optionally sets the commit's scope and can decide to exclude commits from further processing. +commit_parsers = [ + { message = "^[Cc]hore\\([Rr]elease\\): prepare for", skip = true }, + { message = "(^[Mm]erge)|([Mm]erge conflict)", skip = true }, + { field = "author.name", pattern = "dependabot*", group = "Updates" }, + { message = "([Ss]ecurity)|([Vv]uln)", group = "Security" }, + { body = "(.*[Ss]ecurity)|([Vv]uln)", group = "Security" }, + { message = "([Cc]hore\\(lint\\))|(style)|(lint)|(codeql)|(golangci)", group = "Code quality" }, + { message = "(^[Dd]oc)|((?i)readme)|(badge)|(typo)|(documentation)", group = "Documentation" }, + { message = "(^[Ff]eat)|(^[Ee]nhancement)", group = "Implemented enhancements" }, + { message = "(^ci)|(\\(ci\\))|(fixup\\s+ci)|(fix\\s+ci)|(license)|(example)", group = "Miscellaneous tasks" }, + { message = "^test", group = "Testing" }, + { message = "(^fix)|(panic)", group = "Fixed bugs" }, + { message = "(^refact)|(rework)", group = "Refactor" }, + { message = "(^[Pp]erf)|(performance)", group = "Performance" }, + { message = "(^[Cc]hore)", group = "Miscellaneous tasks" }, + { message = "^[Rr]evert", group = "Reverted changes" }, + { message = "(upgrade.*?go)|(go\\s+version)", group = "Updates" }, + { message = ".*", group = "Other" }, +] +# Exclude commits that are not matched by any commit parser. +filter_commits = false +# An array of link parsers for extracting external references, and turning them into URLs, using regex. +link_parsers = [] +# Include only the tags that belong to the current branch. +use_branch_tags = false +# Order releases topologically instead of chronologically. +topo_order = false +# Order releases topologically instead of chronologically. +topo_order_commits = true +# Order of commits in each group/release within the changelog. +# Allowed values: newest, oldest +sort_commits = "newest" +# Process submodules commits +recurse_submodules = false + +#[remote.github] +#owner = "go-openapi" diff --git a/.github/.markdownlint.yml b/.github/.markdownlint.yml new file mode 100644 index 0000000..b25be39 --- /dev/null +++ b/.github/.markdownlint.yml @@ -0,0 +1,30 @@ +# See rules documentation: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md + +# Default state for all rules +default: true + +# ul-style # default: consistent +MD004: consistent + +# hard-tabs +MD010: false + +# line-length +MD013: false + +# no-duplicate-header +MD024: + siblings_only: true + +#single-title +MD025: true + +# ol-prefix +MD029: + style: one_or_ordered + +# no-inline-html +MD033: false + +# fenced-code-language +MD040: false diff --git a/.github/.spellcheck.yml b/.github/.spellcheck.yml new file mode 100644 index 0000000..3906aed --- /dev/null +++ b/.github/.spellcheck.yml @@ -0,0 +1,32 @@ +spellchecker: hunspell +matrix: + - name: markdown + hunspell: + d: en_us + ignore-case: true + pipeline: + - pyspelling.filters.url: + urls: true + emails: true + - pyspelling.filters.markdown: + markdown_extensions: + - pymdownx.superfences + - pymdownx.arithmatex + - md_in_html + - markdown.extensions.extra: + - pyspelling.filters.html: + comments: false + attributes: + - alt + ignores: + - ':matches(code, pre)' + - 'code' + - 'pre' + - 'blockquote' + dictionary: + wordlists: + - .github/.wordlist.txt + output: .github/spellcheck.dic + default_encoding: utf-8 + sources: + - '**.md' diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt new file mode 100644 index 0000000..fab8e7c --- /dev/null +++ b/.github/.wordlist.txt @@ -0,0 +1,108 @@ +API +BSON +CI +CIDR +CLI +DCO +DockerHub +FAQ +GC +GoDoc +Godoc +HUGO +ID +IDs +IP +IPs +ISBN +Kubernetes +Markdown +OAI +OpenAPI +PR +PRs +README +SSN +TODO +UI +ULID +URI +URL +USD +UUID +api +assignees +backquote +backquoted +bash +benchmarking +bitmask +bson +ci +cidr +codecov +codegen +config +customizable +dependabot +docker +e.g. +faq +flattener +fuzzying +gc +github +go-openapi +godoc +golang +golangci +hostname +html +http +https +hugo +i.e. +id +initialism +initialisms +ipsum +ipsums +ipv4 +ipv6 +isbn +json +jsonschema +k8s +kubernetes +linter +linters +loren +markdown +marshaling +mixin +oai +openapi +rebase +rebased +repo +repos +schema +schemas +sexualized +ssn +submodule +subpackage +swagger +toolchain +ui +ulid +unmarshaling +untyped +uri +url +utf-8 +uuid +validator +workspace +workspaces +yaml diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..b02b363 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,214 @@ +## Contribution Guidelines + +You'll find below general guidelines, which mostly correspond to standard practices for open sourced repositories. + +>**TL;DR** +> +> If you're already an experience go developer on github, then you should just feel at home with us +> and you may well skip the rest of this document. +> +> You'll essentially find the usual guideline for a go library project on github. + +These guidelines are general to all libraries published on github by the `go-openapi` organization. + +You'll find more detailed (or repo-specific) instructions in the [maintainer's docs](../docs). + +## How can I contribute? + +There are many ways in which you can contribute. Here are a few ideas: + + * Reporting Issues / Bugs + * Suggesting Improvements + * Code + * bug fixes and new features that are within the main project scope + * improving test coverage + * addressing code quality issues + * Documentation + * Art work that makes the project look great + +## Questions & issues + +### Asking questions + +You may inquire about anything about this library by reporting a "Question" issue on github. + +### Reporting issues + +Reporting a problem with our libraries _is_ a valuable contribution. + +You can do this on the github issues page of this repository. + +Please be as specific as possible when describing your issue. + +Whenever relevant, please provide information about your environment (go version, OS). + +Adding a code snippet to reproduce the issue is great, and as a big time saver for maintainers. + +### Triaging issues + +You can help triage issues which may include: + +* reproducing bug reports +* asking for important information, such as version numbers or reproduction instructions +* answering questions and sharing your insight in issue comments + +## Code contributions + +### Pull requests are always welcome + +We are always thrilled to receive pull requests, and we do our best to +process them as fast as possible. + +Not sure if that typo is worth a pull request? Do it! We will appreciate it. + +If your pull request is not accepted on the first try, don't be discouraged! +If there's a problem with the implementation, hopefully you received feedback on what to improve. + +If you have a lot of ideas or a lot of issues to solve, try to refrain a bit and post focused +pull requests. +Think that they must be reviewed by a maintainer and it is easy to lost track of things on big PRs. + +We're trying very hard to keep the go-openapi packages lean and focused. +These packages constitute a toolkit: it won't do everything for everybody out of the box, +but everybody can use it to do just about everything related to OpenAPI. + +This means that we might decide against incorporating a new feature. + +However, there might be a way to implement that feature *on top of* our libraries. + +### Environment + +You just need a `go` compiler to be installed. No special tools are needed to work with our libraries. + +The go compiler version required is always the old stable (latest minor go version - 1). + +If you're already used to work with `go` you should already have everything in place. + +Although not required, you'll be certainly more productive with a local installation of `golangci-lint`, +the meta-linter our CI uses. + +If you don't have it, you may install it like so: + +```sh +go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest +``` + +### Conventions + +#### Git flow + +Fork the repo and make changes to your fork in a feature branch. + +To submit a pull request, push your branch to your fork (e.g. `upstream` remote): +github will propose to open a pull request on the original repository. + +Typically you'd follow some common naming conventions: + +- if it's a bugfix branch, name it `fix/XXX-something`where XXX is the number of the + issue on github +- if it's a feature branch, create an enhancement issue to announce your + intentions, and name it `feature/XXX-something` where XXX is the number of the issue. + +> NOTE: we don't enforce naming conventions on branches: it's your fork after all. + +#### Tests + +Submit unit tests for your changes. + +Go has a great built-in test framework ; use it! + +Take a look at existing tests for inspiration, and run the full test suite on your branch +before submitting a pull request. + +Our CI measures test coverage and the test coverage of every patch. +Although not a blocking step - because there are so many special cases - +this is an indicator that maintainers consider when approving a PR. + +Please try your best to cover about 80% of your patch. + +#### Code style + +You may read our stance on code style [there](../docs/STYLE.md). + +#### Documentation + +Don't forget to update the documentation when creating or modifying features. + +Most documentation for this library is directly found in code as comments for godoc. + +The documentation for the go-openapi packages is published on the public go docs site: + + + +Check your documentation changes for clarity, concision, and correctness. + +If you want to assess the rendering of your changes when published to `pkg.go.dev`, you may +want to install the `pkgsite` tool proposed by `golang.org`. + +```sh +go install golang.org/x/pkgsite/cmd/pkgsite@latest +``` + +Then run on the repository folder: +```sh +pkgsite . +``` + +This wil run a godoc server locally where you may see the documentation generated from your local repository. + +#### Commit messages + +Pull requests descriptions should be as clear as possible and include a +reference to all the issues that they address. + +Pull requests must not contain commits from other users or branches. + +Commit messages are not required to follow the "conventional commit" rule, but it's certainly a good +thing to follow this guidelinea (e.g. "fix: blah blah", "ci: did this", "feat: did that" ...). + +The title in your commit message is used directly to produce our release notes: try to keep them neat. + +The commit message body should detail your changes. + +If an issue should be closed by a commit, please add this reference in the commit body: + +``` +* fixes #{issue number} +``` + +#### Code review + +Code review comments may be added to your pull request. + +Discuss, then make the suggested modifications and push additional commits to your feature branch. + +Be sure to post a comment after pushing. The new commits will show up in the pull +request automatically, but the reviewers will not be notified unless you comment. + +Before the pull request is merged, +**make sure that you squash your commits into logical units of work** +using `git rebase -i` and `git push -f`. + +After every commit the test suite should be passing. + +Include documentation changes in the same commit so that a revert would remove all traces of the feature or fix. + +#### Sign your work + +The sign-off is a simple line at the end of your commit message, +which certifies that you wrote it or otherwise have the right to +pass it on as an open-source patch. + +We require the simple DCO below with an email signing your commit. +PGP-signed commit are greatly appreciated but not required. + +The rules are pretty simple: + +* read our [DCO](./DCO.md) (from [developercertificate.org](http://developercertificate.org/)) +* if you agree with these terms, then you just add a line to every git commit message + + Signed-off-by: Joe Smith + +using your real name (sorry, no pseudonyms or anonymous contributions.) + +You can add the sign off when creating the git commit via `git commit -s`. diff --git a/.github/DCO.md b/.github/DCO.md new file mode 100644 index 0000000..e168dc4 --- /dev/null +++ b/.github/DCO.md @@ -0,0 +1,40 @@ + # Developer's Certificate of Origin + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..9ce426e --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,52 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "friday" + open-pull-requests-limit: 2 # <- default is 5 + groups: # <- group all github actions updates in a single PR + # 1. development-dependencies are auto-merged + development-dependencies: + patterns: + - '*' + + - package-ecosystem: "gomod" + # We define 4 groups of dependencies to regroup update pull requests: + # - development (e.g. test dependencies) + # - go-openapi updates + # - golang.org (e.g. golang.org/x/... packages) + # - other dependencies (direct or indirect) + # + # * All groups are checked once a week and each produce at most 1 PR. + # * All dependabot PRs are auto-approved + # + # Auto-merging policy, when requirements are met: + # 1. development-dependencies are auto-merged + # 2. golang.org-dependencies are auto-merged + # 3. go-openapi patch updates are auto-merged. Minor/major version updates require a manual merge. + # 4. other dependencies require a manual merge + directory: "/" + schedule: + interval: "weekly" + day: "friday" + open-pull-requests-limit: 4 + groups: + development-dependencies: + patterns: + - "github.com/stretchr/testify" + + golang-org-dependencies: + patterns: + - "golang.org/*" + + go-openapi-dependencies: + patterns: + - "github.com/go-openapi/*" + + other-dependencies: + exclude-patterns: + - "github.com/go-openapi/*" + - "github.com/stretchr/testify" + - "golang.org/*" diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..1213e34 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,58 @@ +# CI workflows + +## Shared workflows + +### Dependencies automation + +* auto-merge.yml: + * auto-merge dependabot updates, with dependency group rules + * auto-merge go-openapi bot updates + +### Test automation + +* go-test.yml: go unit tests +* monorepo-go-test.yml: go unit tests for monorepos + +* collect-coverage.yml: (common) collect & publish test coverage (to codecov) +* collect-reports.yml: (common) collect & publish test reports (to codecov and github) + +### Security + +* codeql.yml: CodeQL workflow for go and github actions +* scanner.yml: trivy & govulncheck scans + +### Release automation + +* bump-release.yml: manually triggered workflow to cut a release +* tag-release.yml: cut a release on push tag +* release.yml: (common) release & release notes build + +### Code quality + +* collect-coverage.yml: common collect & publish test coverage (to codecov) +* collect-reports.yml: common collect & publish test reports (to codecov and github) + +### Documentation quality + +* contributors.yml: updates CONTRIBUTORS.md +* doc-update.yml: lint & spellcheck on markdown updates +* pr-comment.yml: common PR commment workflow + +## Test workflows + +* local-auto-merge.yml +* local-bump-release.yml +* local-codeql.yml +* local-contributors.yml +* local-doc-update.yml +* local-go-test.yml +* local-monorepo-go-test.yml +* local-release.yml +* local-scanner.yml +* local-tag-release.yml + +## Configuration files + +* .cliff + +scripts diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml new file mode 100644 index 0000000..7272f72 --- /dev/null +++ b/.github/workflows/auto-merge.yml @@ -0,0 +1,58 @@ +name: Dependabot auto-merge + +on: + workflow_call: + +permissions: + contents: read + +defaults: + run: + shell: bash + +jobs: + dependabot: + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-latest + if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }} + steps: + - + name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@08eff52bf64351f401fb50d4972fa95b9f2c2d1b # v2.4.0 + - + name: Auto-approve all dependabot PRs + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + run: gh pr review --approve "$PR_URL" + - + name: Auto-merge dependabot PRs for development dependencies + if: ${{ contains(steps.metadata.outputs.dependency-group, 'development-dependencies') }} + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + run: gh pr merge --auto --rebase "$PR_URL" + + actions-bot: + # description: | + # Auto merge for go-openapi bot. + # The regular actions-bot user cannot approve and merge its own pull requests, + # We use another bot user to update content to be approved/auto-merged by actions-bot. + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-latest + if: ${{ github.event.pull_request.user.login == 'bot-go-openapi[bot]' }} + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + steps: + - + name: Auto-approve all bot-go-openapi PRs + run: gh pr review --approve "$PR_URL" + - + name: Auto-merge bot-go-openapi PRs + run: gh pr merge --auto --rebase "$PR_URL" diff --git a/.github/workflows/bump-release.yml b/.github/workflows/bump-release.yml new file mode 100644 index 0000000..f476da6 --- /dev/null +++ b/.github/workflows/bump-release.yml @@ -0,0 +1,133 @@ +name: Bump Release + +permissions: + contents: read + +# description: | +# Manual action to bump the current version and cut a release. +# +# Determine which version to bump. +# Push corresponding tag, with comment. +# Build a github release on pushed tag. + +defaults: + run: + shell: bash + +on: + workflow_call: + inputs: + bump-patch: + description: Bump a patch version release + type: boolean + required: false + default: true + bump-minor: + description: Bump a minor version release + type: boolean + required: false + default: false + bump-major: + description: Bump a major version release + type: boolean + required: false + default: false + tag-message-title: + description: Tag message title to prepend to the release notes + required: false + type: string + tag-message-body: + description: | + Tag message body to prepend to the release notes. + (use "|" to replace end of line). + required: false + type: string + +jobs: + tag-release: + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + next-tag: ${{ steps.bump-release.outputs.next-tag }} + steps: + - + name: Checkout code + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + fetch-depth: 0 + - + name: install svu + uses: go-openapi/gh-actions/install/svu@00fc74b63fc83dd6031018ecbeac387ca9131fe2 # v0.1.0 + - + name: Bump release + id: bump-release + run: | + # determine next tag to push + NEXT_TAG="" + if [[ '${{ inputs.bump-patch }}' == 'true' ]] ; then + NEXT_TAG=$(svu patch) + elif [[ '${{ inputs.bump-minor }}' == 'true' ]] ; then + NEXT_TAG=$(svu minor) + elif [[ '${{ inputs.bump-major }}' == 'true' ]] ; then + NEXT_TAG=$(svu major) + else + echo "::error::invalid options::One of bump-patch, bump-minor or bump-major must be true" + exit 1 + fi + + if [[ -z "${NEXT_TAG}" ]] ; then + echo "::error::something went wrong and no tag has been determined. Stopping here" + exit 1 + fi + + echo "next-tag=${NEXT_TAG}" >> "$GITHUB_OUTPUT" + echo "::notice title=next-tag:${NEXT_TAG}" + - + name: Import GPG key + uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0 + # This is using the GPG signature of bot-go-openapi. + # + # CI_BOT_GPG_PRIVATE_KEY: the bot gpg key, armored + # CI_BOT_GPG_PASSPHRASE: the bot gpg passphrase + # CI_BOT_SIGNING_KEY: the fingerprint of the subkey used (space removed) + # NOTE(fredbi): extracted w/ gpg -K --homedir gnupg --keyid-format LONG --with-keygrip --fingerprint --with-subkey-fingerprint + with: + gpg_private_key: ${{ secrets.CI_BOT_GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.CI_BOT_GPG_PASSPHRASE }} + fingerprint: ${{ secrets.CI_BOT_SIGNING_KEY }} + git_user_signingkey: true + git_commit_gpgsign: true + git_tag_gpgsign: true + - + name: Create and sign tag + env: + NEXT_TAG: ${{ steps.bump-release.outputs.next-tag }} + MESSAGE_TITLE: ${{ inputs.tag-message-title }} + MESSAGE_BODY: ${{ inputs.tag-message-body }} + run: | + # construct the tag message + set -x + MESSAGE="${MESSAGE_TITLE}" + if [[ -n "${MESSAGE_BODY}" ]] ; then + BODY=$(echo "${MESSAGE_BODY}"|tr '|' '\n') + MESSAGE=$(printf "%s\n%s\n" "${MESSAGE}" "${BODY}") + fi + echo "::notice title=tag-message:${MESSAGE}" + + git tag -s -m "${MESSAGE}" "${NEXT_TAG}" + git tag -v "${NEXT_TAG}" + git push origin "${NEXT_TAG}" + + gh-release: + # trigger release creation explictly. + # The previous tagging action does not trigger the normal release workflow + # (github prevents cascading triggers from happening). + name: Create release + needs: [ tag-release ] + permissions: + contents: write + uses: ./.github/workflows/release.yml + with: + tag: ${{ needs.tag-release.outputs.next-tag}} + secrets: inherit diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..e8cbfa4 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,34 @@ +name: "CodeQL" + +on: + workflow_call: + +permissions: + contents: read + +jobs: + analyze: + name: Analyze. + runs-on: ubuntu-latest + timeout-minutes: 360 + permissions: + contents: read + security-events: write + # actions: read # <- is needed only for private repositories + strategy: + fail-fast: false + matrix: + language: ['go','actions'] + steps: + - + name: Checkout repository + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - + # Initializes the CodeQL tools for scanning. + name: Initialize CodeQL + uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 + with: + languages: ${{ matrix.language }} + - + name: Analyze ${{ matrix.language }} + uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 diff --git a/.github/workflows/collect-coverage.yml b/.github/workflows/collect-coverage.yml new file mode 100644 index 0000000..66443e9 --- /dev/null +++ b/.github/workflows/collect-coverage.yml @@ -0,0 +1,39 @@ +name: Collect coverage + +permissions: + pull-requests: read + contents: read + +on: + workflow_call: + +jobs: + collect-coverage: + # description: | + # Gather, merge then uploads test coverage files from all test jobs (this includes integration tests, + # like codegen-test). This reduces the number of failures due to codecov hitting github API rate limit. + name: collect test coverage + runs-on: ubuntu-latest + steps: + - + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + - + name: Download coverage artifacts + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + with: + run-id: "${{ github.run_id }}" + pattern: "*.coverage.*" + # artifacts resolve as folders + path: coverage/ + - + name: Upload coverage to codecov + uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + with: + name: Aggregated coverage + # All *.coverage.*.out files uploaded should be detected by the codecov action. + # NOTE: we lose the flags on individual test reports (e.g. by os, by go version, unit vs integration tests) + fail_ci_if_error: false + verbose: false diff --git a/.github/workflows/collect-reports.yml b/.github/workflows/collect-reports.yml new file mode 100644 index 0000000..9da967b --- /dev/null +++ b/.github/workflows/collect-reports.yml @@ -0,0 +1,115 @@ +name: Collect test reports + +permissions: + pull-requests: read + contents: read + +on: + workflow_call: + +defaults: + run: + shell: bash + +jobs: + # description: | + # Gather, merge then uploads test report files from unit test jobs. + # + # At this moment test reports are published on both codecov + # (see ) and the github actions UI + # (see ). + collect-reports: + name: collect test reports + runs-on: ubuntu-latest + steps: + - + name: Download test report artifacts + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + with: + run-id: "${{ github.run_id }}" + pattern: "*.report.*" + # artifacts resolve as folders + path: reports/ + - + name: Install go-junit-report + uses: go-openapi/gh-actions/install/go-junit-report@2c8f8152814933c4cead92a51558699238ee9565 + - + name: Convert test reports to a merged JUnit XML + # NOTE: codecov test reports only support JUnit format at this moment. See https://docs.codecov.com/docs/test-analytics. + # Ideally, codecov improve a bit their platform, so we may only need a single pass to CTRF format. + # + # As a contemplated alternative, we could use gotestsum above to produce the JUnit XML directly. + # At this moment, we keep a json format to dispatch test reports to codecov as well as to CTRF reports. + # + # TODO(fredbi): investigate - use mikepenz/action-junit-report@v5, that packages most of the following scripts + # in a single action. Alternative: for that action. + run: | + find reports/ -name \*.json -print0 | xargs -0 cat | go-junit-report -parser gojson -out=reports/junit_report.xml + - + name: Upload test results to Codecov + # This allows for using the test results UI on codecov + uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + with: + files: '**/junit_report.xml' + report_type: 'test_results' + fail_ci_if_error: false + handle_no_reports_found: true + verbose: true + - + name: Install go-ctrf-json-reporter + uses: go-openapi/gh-actions/install/go-ctrf-json-reporter@2c8f8152814933c4cead92a51558699238ee9565 + - + name: Convert test reports to CTRF JSON + # description: | + # This step publishes CTRF test reports on github UI (actions) + run: | + appName="${{ github.repository }}" + buildNumber="${{ github.run_id }}" + appVersion="${{ github.event.pull_request.head.sha }}" + if [[ -z "${appVersion}" ]] ; then + # for push events + appVersion="${{ github.sha }}" + fi + + # reconstruct platform information from the file name + while read -r report ; do + reformated=$(echo "${report##*/}"|sed -E 's/(go)([[:digit:]]+)\.([[:digit:]]+)/\1\2\3/') # e.g. go1.24 becomes go124 + mapfile -d'.' -t -s 2 -n 2 split < <(echo "$reformated") # skip the first 2 parts, stop on 2 more parts + envstring="${split[0]}" + osPlatform="${envstring%-*}" + osRelease="${envstring##*-}" + + # this is a best effort only: tests may be cancelled upstream and produce incorrect reports + go-ctrf-json-reporter \ + -quiet \ + -appName "${appName}" \ + -appVersion "${appVersion}" \ + -buildNumber "${buildNumber}" \ + -osPlatform "${osPlatform}" \ + -osRelease "${osRelease}" \ + -output "./reports/ctrf_report_${osPlatform}_${osRelease}.json" < "${report}" || true + done < <(find reports -name \*.json) + + # NOTE: at this moment, we don't upload CTRF reports as artifacts. + # Some of the CTRF reports are therefore not available (flaky tests, history, ...). + # + # See https://github.com/ctrf-io/github-test-reporter?tab=readme-ov-file#report-showcase + # for more reporting possibilities. At the moment, we keep it simple, as most advanced features + # require a github token (thus adding the complexity of a separate workflow starting on pull_request_target). + # + # For the moment, we are contented with these simple reports. This is an opportunity to compare the insight they + # provide as compared to what is uploaded to codecov. + # + # Codecov analytics are pretty poor at this moment. On the other hand, they manage the bot that pushes back + # PR comments. + # + # They also handle the storage of past test reports, so as to assess flaky tests. + - + name: Publish Test Summary Results + uses: ctrf-io/github-test-reporter@024bc4b64d997ca9da86833c6b9548c55c620e40 # v1.0.26 + with: + report-path: 'reports/ctrf_report_*.json' + use-suite-name: true + summary-report: true # post a report to the github actions summary + github-report: true + failed-folded-report: true diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml new file mode 100644 index 0000000..92a0ee8 --- /dev/null +++ b/.github/workflows/contributors.yml @@ -0,0 +1,85 @@ +name: Contributors + +on: + workflow_call: + +permissions: + contents: read + +jobs: + update-contributors: + name: all-time contributors + permissions: + pull-requests: write + contents: write + runs-on: ubuntu-latest + outputs: + pull-request-url: ${{ steps.create-pull-request.outputs.pull-request-url }} + steps: + - + name: Checkout repository + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - + name: Identify all-time contributors to this repository + uses: github/contributors@abf36819e840f6e8056dcd40d33003ce7c4bc8dd # v1.7.6 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPOSITORY: ${{ github.repository }} + LINK_TO_PROFILE: 'True' + - + name: Rename contributor file + run: | + rm -rf contributors.json + mv contributors.md CONTRIBUTORS.md + - + name: Switch to go-openapi bot user + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + id: app-token + with: + app-id: ${{ secrets.CI_BOT_APP_ID }} + private-key: ${{ secrets.CI_BOT_APP_PRIVATE_KEY }} + - + name: Import GPG key + uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0 + with: + gpg_private_key: ${{ secrets.CI_BOT_GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.CI_BOT_GPG_PASSPHRASE }} + fingerprint: ${{ secrets.CI_BOT_SIGNING_KEY }} + git_user_signingkey: true + git_commit_gpgsign: true + git_tag_gpgsign: true + - + name: Create a PR + id: create-pull-request + uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9 + with: + commit-message: "doc: updated contributors file" + branch: doc/contributors-bot + delete-branch: true + title: "doc: updated contributors file" + token: ${{ steps.app-token.outputs.token }} + labels: "bot" + draft: false + assignees: fredbi + reviewers: fredbi + sign-commits: true + signoff: false + + auto-merge: + # description: | + # Validates and merge the pull request triggered above. + needs: [update-contributors] + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-latest + env: + PR_URL: ${{needs.update-contributors.outputs.pull-request-url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + steps: + - + name: Auto-approve PR + run: gh pr review --approve "$PR_URL" + - + name: Auto-merge PR + run: gh pr merge --auto --rebase "$PR_URL" diff --git a/.github/workflows/fuzz-test.yml b/.github/workflows/fuzz-test.yml new file mode 100644 index 0000000..2d01b5b --- /dev/null +++ b/.github/workflows/fuzz-test.yml @@ -0,0 +1,123 @@ +name: fuzz test + +permissions: + pull-requests: read + contents: read + +on: + workflow_call: + +defaults: + run: + shell: bash + +# TODO: +# * support multiple fuzzed packages +# * support mono-repo setup + +jobs: + fuzz-test: + name: fuzz test + runs-on: ubuntu-latest + env: + CORPUS_MAX_SIZE_MB: 250 + FUZZ_TIME: 1m30s + FUZZ_MINIMIZE_TIME: 5m + steps: + - + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - + uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + with: + go-version: stable + check-latest: true + cache: true + - + name: Locate go fuzz cache + run: | + GOCACHE=$(go env GOCACHE) + echo "CORPUS_DIR=${GOCACHE}/fuzz" >> "${GITHUB_ENV}" + - + name: Retrieve fuzz corpus from cache + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + key: ${{ runner.os }}-go-fuzz + path: + ${{ env.CORPUS_DIR }} + - + name: Manage fuzz corpus cache size + run: | + mkdir -p "${CORPUS_DIR}" + # This script checks that the size of the corpus cache doesn't exceed ${CORPUS_MAX_SIZE_MB}, + # and if it does, it removes all oldest files beyond that size. + + function size() { + local location=$1 + local unit=$2 + + du -s"${unit}" "${location}"|cut -f1 + } + + function purge() { + local location=$1 + local max_size_b=$2 + declare -i current_size_b=0 file_size_b=0 purged_files=0 + + while read -r filename ; do + file_size_b="$(size "${filename}" "b")" + ((current_size_b+=file_size_b)) + + if [[ ${current_size_b} -le ${max_size_b} ]] ; then + continue + fi + if [[ ${file_size_b} -eq 0 ]] ; then + continue + fi + + rm -f "${filename}" + ((purged_files+=1)) + done < <(find "${location}" -type f -print0 | xargs -0 ls -t) + + echo ${purged_files} + } + + CURRENT_SIZE_MB="$(size "${CORPUS_DIR}" "m")" + if [[ "${CURRENT_SIZE_MB}" -lt "${MAX_SIZE_MB}" ]] ; then + echo "::notice:cache size remains under the accepted size of ${MAX_SIZE_MB} MB: ${CURRENT_SIZE_MB} MB" + + exit 0 + fi + + declare -i max_size_b=$(("${CORPUS_MAX_SIZE_MB}" * 1024 * 1024)) + purged_files=$(purge "${purged_dir}" "${max_size_b}"); + echo "::notice:cache size is ${CURRENT_SIZE_MB} MB: purging oldest files to keep it under ${CORPUS_MAX_SIZE_MB} MB" + if [[ ${purged_files} -gt 0 ]] ; then + echo "::notice:removed ${purged_files} files to keep the cache size below ${CORPUS_MAX_SIZE_MB} MB" + fi + FINAL_SIZE_MB="$(size "${CORPUS_DIR}" "m")" + echo "::notice:purged cache size: ${FINAL_SIZE_MB} MB" + - + name: Run go fuzz tests + # TODO(fredbi): ./... is not supported: we should run as a matrix test multiple fuzz tests + run: > + go test + -fuzz=Fuzz + -run=Fuzz + -fuzztime='${{ env.FUZZ_TIME }}' + -fuzzminimizetime='${{ env.FUZZ_MINIMIZE_TIME }}' + ./... + - + name: Upload failed corpus + if: ${{ failure() }} + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + # TODO(fredbi): ideally, after uploading, we should fire a pull request to add + # this corpus to testdata. + with: + path: ${{ env.CORPUS_DIR }} + name: '${{ runner.os }}-fuzz-corpus-failure' + retention-days: 60 + - + name: Report fuzz corpus cache size + run: | + FINAL_SIZE=$(du -m "${CORPUS_DIR}"|cut -f1) + echo "::notice title=fuzz corpus size:${FINAL_SIZE}MB" diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml new file mode 100644 index 0000000..1827a83 --- /dev/null +++ b/.github/workflows/go-test.yml @@ -0,0 +1,112 @@ +name: go-test + +permissions: + pull-requests: read + contents: read + +on: + workflow_call: + +defaults: + run: + shell: bash + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - + uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + with: + go-version: stable + check-latest: true + cache: true + - + name: golangci-lint + uses: golangci/golangci-lint-action@e7fa5ac41e1cf5b7d48e45e42232ce7ada589601 # v9.1.0 + with: + version: latest + only-new-issues: true + skip-cache: true + + test: + name: Unit tests + runs-on: ${{ matrix.os }} + needs: [lint] + + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + go: ['oldstable', 'stable' ] + + steps: + - + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - + uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + with: + go-version: '${{ matrix.go }}' + check-latest: true + cache: true + - + name: Install gotestsum + uses: go-openapi/gh-actions/install/gotestsum@2c8f8152814933c4cead92a51558699238ee9565 + - + name: Run unit tests + run: > + gotestsum + --jsonfile 'unit.report.${{ matrix.os }}-${{ matrix.go }}.json' + -- + -race + -p 2 + -count 1 + -timeout=20m + -coverprofile='unit.coverage.${{ matrix.os }}-${{ matrix.go }}.out' + -covermode=atomic + -coverpkg="$(go list)"/... + ./... + - + name: Upload coverage artifacts + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + # *.coverage.* pattern is automatically detected by codecov + path: '**/*.coverage.*.out' + name: 'unit.coverage.${{ matrix.os }}-${{ matrix.go }}' + retention-days: 1 + - + name: Upload test report artifacts + # upload report even if test fail. BTW, this is when they are valuable. + if: ${{ !cancelled() }} + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + path: '**/unit.report.*.json' + name: 'unit.report.${{ matrix.os }}-${{ matrix.go }}' + retention-days: 1 + + fuzz-test: + uses: ./.github/workflows/fuzz-test.yml + + test-complete: + # description: | + # Be explicit about all tests being passed. This allows for setting up only a few status checks on PRs. + name: tests completed + needs: [test,fuzz-test] + runs-on: ubuntu-latest + steps: + - + name: Tests completed + run: | + echo "::notice title=Success:All tests passed" + + collect-coverage: + needs: [test-complete] + if: ${{ !cancelled() && needs.test-complete.result == 'success' }} + uses: ./.github/workflows/collect-coverage.yml + + collect-reports: + needs: [test] + if: ${{ !cancelled() }} + uses: ./.github/workflows/collect-reports.yml diff --git a/.github/workflows/local-auto-merge.yml b/.github/workflows/local-auto-merge.yml new file mode 100644 index 0000000..e5db6ea --- /dev/null +++ b/.github/workflows/local-auto-merge.yml @@ -0,0 +1,18 @@ +name: Dependabot auto-merge + +permissions: + contents: read + +# description: | +# This workflow mimics how a go-openapi repo would typically invoke the common workflow. + +on: + pull_request: + +jobs: + dependabot: + permissions: + contents: write + pull-requests: write + uses: ./.github/workflows/auto-merge.yml + secrets: inherit diff --git a/.github/workflows/local-bump-release.yml b/.github/workflows/local-bump-release.yml new file mode 100644 index 0000000..d00136b --- /dev/null +++ b/.github/workflows/local-bump-release.yml @@ -0,0 +1,53 @@ +name: Bump Release + +permissions: + contents: read + +# description: | +# Manual action to bump the current version and cut a release. +# +# Determine which version to bump. +# Push corresponding tag, with comment. +# Build a github release on pushed tag. + +on: + workflow_dispatch: + inputs: + bump-patch: + description: Bump a patch version release + type: boolean + required: false + default: true + bump-minor: + description: Bump a minor version release + type: boolean + required: false + default: false + bump-major: + description: Bump a major version release + type: boolean + required: false + default: false + tag-message-title: + description: Tag message title to prepend to the release notes + required: false + type: string + tag-message-body: + description: | + Tag message body to prepend to the release notes. + (use "|" to replace end of line). + required: false + type: string + +jobs: + bump-release: + permissions: + contents: write + uses: ./.github/workflows/bump-release.yml + with: + bump-patch: ${{ inputs.bump-patch }} + bump-minor: ${{ inputs.bump-minor }} + bump-major: ${{ inputs.bump-major }} + tag-message-title: ${{ inputs.tag-message-title }} + tag-message-body: ${{ inputs.tag-message-body }} + secrets: inherit diff --git a/.github/workflows/local-codeql.yml b/.github/workflows/local-codeql.yml new file mode 100644 index 0000000..264b152 --- /dev/null +++ b/.github/workflows/local-codeql.yml @@ -0,0 +1,22 @@ +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + paths-ignore: # remove this clause if CodeQL is a required check + - '**/*.md' + schedule: + - cron: '39 19 * * 5' + +permissions: + contents: read + +jobs: + codeql: + permissions: + contents: read + security-events: write + uses: ./.github/workflows/codeql.yml + secrets: inherit diff --git a/.github/workflows/local-contributors.yml b/.github/workflows/local-contributors.yml new file mode 100644 index 0000000..41c731e --- /dev/null +++ b/.github/workflows/local-contributors.yml @@ -0,0 +1,18 @@ +name: Contributors + +on: + schedule: + - cron: '18 4 * * 6' + + workflow_dispatch: + +permissions: + contents: read + +jobs: + contributors: + permissions: + pull-requests: write + contents: write + uses: ./.github/workflows/contributors.yml + secrets: inherit diff --git a/.github/workflows/local-go-test.yml b/.github/workflows/local-go-test.yml new file mode 100644 index 0000000..8972a7a --- /dev/null +++ b/.github/workflows/local-go-test.yml @@ -0,0 +1,20 @@ +name: go test + +permissions: + pull-requests: read + contents: read + +# description: | +# This workflow mimics how a go-openapi repo would typically invoke the common workflow. + +on: + push: + branches: + - master + + pull_request: + +jobs: + test: + uses: ./.github/workflows/go-test.yml + secrets: inherit diff --git a/.github/workflows/local-release.yml b/.github/workflows/local-release.yml new file mode 100644 index 0000000..fbe6f16 --- /dev/null +++ b/.github/workflows/local-release.yml @@ -0,0 +1,18 @@ +name: Release + +on: + push: + tags: + - v[0-9]+* + +permissions: + contents: read + +jobs: + release: + permissions: + contents: write + uses: ./.github/workflows/release.yml + with: + tag: ${{ github.ref_name }} + secrets: inherit diff --git a/.github/workflows/local-scanner.yml b/.github/workflows/local-scanner.yml new file mode 100644 index 0000000..3e1a0cc --- /dev/null +++ b/.github/workflows/local-scanner.yml @@ -0,0 +1,18 @@ +name: Vulnerability scans + +on: + branch_protection_rule: + push: + branches: [ "master" ] + schedule: + - cron: '18 4 * * 3' + +permissions: read-all + +jobs: + release: + permissions: + contents: read + security-events: write + uses: ./.github/workflows/scanner.yml + secrets: inherit diff --git a/.github/workflows/local-tag-release.yml b/.github/workflows/local-tag-release.yml new file mode 100644 index 0000000..e61b58a --- /dev/null +++ b/.github/workflows/local-tag-release.yml @@ -0,0 +1,24 @@ +name: Release on tag + +permissions: + contents: read + +# description: | +# Build a github release on pushed tag. +# +# The only available asset is a release note. + +on: + push: + tags: + - v[0-9]+* + +jobs: + gh-release: + name: Create release + permissions: + contents: write + uses: ./.github/workflows/release.yml + with: + tag: ${{ github.ref_name }} + secrets: inherit diff --git a/.github/workflows/markdown-changed.yml b/.github/workflows/markdown-changed.yml new file mode 100644 index 0000000..199ee61 --- /dev/null +++ b/.github/workflows/markdown-changed.yml @@ -0,0 +1,33 @@ +name: Markdown changed + +on: + workflow_call: + +permissions: + contents: read + +jobs: + markdown-changed: + # description: | + # This triggers a markdown and spellcheck lint whenever documentation files change. + runs-on: ubuntu-latest + outputs: + proceed: ${{ steps.changed-markdown-files.outputs.any_changed }} + all_changed_files: ${{ steps.changed-markdown-files.outputs.all_changed_files }} + steps: + - + name: Originating repo checkout (e.g. public fork) + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + ref: ${{ github.event.pull_request.head.sha }} + - + name: Get changed markdown files + uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v4.7.0 + id: changed-markdown-files + with: + files: '**/*.md' + - + name: Notify + run: | + echo "::notice::Detected some changed markdown files" + echo "${{ steps.changed-markdown-files.outputs.all_changed_files }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a41715d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,70 @@ +name: Release + +permissions: + contents: read + +# description: | +# Build a github release on either pushed tag or a manual workflow call. +# +# At this moment, the only available asset is a release note. + +on: + workflow_call: + inputs: + tag: + type: string + required: true + +jobs: + gh-release: + name: Create release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - + name: Checkout code + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + fetch-depth: 0 + - + name: Extract tag message + id: get-message + # tag message is not retrieved unless we fetch the ref explictly + run: | + set -x + + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + MESSAGE=$(git tag -l '${{ inputs.tag }}' --format='%(contents:subject) + + %(contents:body) + ') + export MESSAGE + { + echo "message<> "${GITHUB_OUTPUT}" + + echo "Message in git tag ${{ inputs.tag }}" + echo "$MESSAGE" + - + name: Generate release notes + # this uses git-cliff to generate a release note from the commit history + id: notes + env: + GITHUB_TOKEN: ${{ github.token }} + GITHUB_REPO: ${{ github.repository }} + uses: orhun/git-cliff-action@d77b37db2e3f7398432d34b72a12aa3e2ba87e51 # v4.6.0 + with: + config: '.cliff.toml' + args: >- + --current + --with-tag-message '${{ steps.get-message.outputs.message }}' + - + name: Create github release + uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2 + with: + body: ${{ steps.notes.outputs.content }} + tag_name: ${{ inputs.tag }} + generate_release_notes: false # skip auto-generated release notes from github API diff --git a/.github/workflows/scanner.yml b/.github/workflows/scanner.yml new file mode 100644 index 0000000..d063e66 --- /dev/null +++ b/.github/workflows/scanner.yml @@ -0,0 +1,65 @@ +name: Vulnerability scans + +on: + workflow_call: + +permissions: read-all + +# description: | +# A fast vulnerability scan on the repo that effectively supplements ossf scorecard and codesql +# and may run every day. +# +# * trivy reports are often more easily actionable than codeql reports. +# * govumnscan supplements this analysis with a more go-specific approach +# +# NOTE: at this moment, we don't want to adopt snyk, which requires a token, provides an extra dashboard etc. +# Most likely, snyk would be redundant with trivy. + +jobs: + analysis: + name: Vulnerability scan + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false + - + name: Vulnerability scan by trivy + uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # v0.33.1 + with: + scan-type: repo + format: sarif + hide-progress: false + output: trivy-code-report.sarif + scanners: vuln,secret + exit-code: 0 + - + name: Upload trivy findings to code scanning dashboard + uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3 + with: + category: trivy + sarif_file: trivy-code-report.sarif + + govulnscan: + name: go vulnerability scan + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - + id: govulncheck + uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4 + with: + output-format: sarif + output-file: govulnscan-report.sarif + - + name: Upload govulnscan findings to code scanning dashboard + uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3 + with: + category: govulnscan + sarif_file: govulnscan-report.sarif diff --git a/.github/workflows/scripts/filter.sed b/.github/workflows/scripts/filter.sed new file mode 100644 index 0000000..3445b3b --- /dev/null +++ b/.github/workflows/scripts/filter.sed @@ -0,0 +1,60 @@ +:scan_input { + # main scanner loop, looking for "Misspelled words:" entries + /^Misspelled words:/,/^-\{5,\}/ { + /^<html\(attribute\|content\)>\;/ { + # Extract the file name + # TODO: be less strict here + s/^<html\(attribute\|content\)>\;\s\+\(.\+\.md\):.*$/"\2": [/ + h + n # skip current match + n # skip next line + + # go decode a section of single words, enclosed between "=====..." lines + b parse_section + } + + n + + b scan_input + } +} + +# Back to main loop (cycles) +d + +:parse_section { + :word /^[^-]/ { + # Append words to the previous line + # Trim space around words + s/\s\+//g + # Quote word + s/^/"/ + s/$/"/ + + # Add comma separator + s/$/, / + H + n + + b word + } + + /^-\{5,\}$/ { + # Print the collected words as a comma-separated array of words + x + + s/^/{/g + s/\n/ /g + s/\s\+/ /g + s/\s\+$//g + s/\(,\s*\)\?$/ ]/ + s/$/}/g + + p + + # Start a new cycle + d + } +} + +d diff --git a/.github/workflows/scripts/merge.jq b/.github/workflows/scripts/merge.jq new file mode 100644 index 0000000..cd63ab5 --- /dev/null +++ b/.github/workflows/scripts/merge.jq @@ -0,0 +1,4 @@ +reduce (.[] | to_entries | .[]) as {$key, $value} ( + {} ; + .[$key] += $value +) | .[] |= unique diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml new file mode 100644 index 0000000..22fa5df --- /dev/null +++ b/.github/workflows/tag-release.yml @@ -0,0 +1,29 @@ +name: Release on tag + +permissions: + contents: read + +# description: | +# Build a github release on pushed tag. +# +# The only available asset is a release note. + +on: + workflow_call: + inputs: + tag: + type: string + required: true + +jobs: + gh-release: + # trigger release creation explictly. + # The previous tagging action does not trigger the normal release workflow + # (github prevents cascading triggers from happening). + name: Create release + permissions: + contents: write + uses: ./.github/workflows/release.yml + with: + tag: ${{ github.ref_name }} + secrets: inherit diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..568ce16 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,75 @@ +version: "2" +linters: + default: all + disable: + - cyclop + - depguard + - errchkjson + - errorlint + - exhaustruct + - forcetypeassert + - funlen + - gochecknoglobals + - gochecknoinits + - gocognit + - godot + - godox + - gosmopolitan + - inamedparam + - intrange # disabled while < go1.22 + - ireturn + - lll + - musttag + - nestif + - nlreturn + - nonamedreturns + - noinlineerr + - paralleltest + - recvcheck + - testpackage + - thelper + - tparallel + - unparam + - varnamelen + - whitespace + - wrapcheck + - wsl + - wsl_v5 + settings: + dupl: + threshold: 200 + goconst: + min-len: 2 + min-occurrences: 3 + gocyclo: + min-complexity: 45 + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ +issues: + # Maximum issues count per one linter. + # Set to 0 to disable. + # Default: 50 + max-issues-per-linter: 0 + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..9322b06 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at ivan+abuse@flanders.co.nz. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..5fe9362 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,13 @@ +# Contributors + +- Repository: ['go-openapi/ci-workflows'] + +| Total Contributors | Total Contributions | +| --- | --- | +| 1 | 27 | + +| Username | All Time Contribution Count | All Commits | +| --- | --- | --- | +| @fredbi | 27 | https://github.com/go-openapi/ci-workflows/commits?author=fredbi | + + _this file was generated by the [Contributors GitHub Action](https://github.com/github/contributors)_ diff --git a/README.md b/README.md index 91b6f26..4e20b1b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,124 @@ # ci-workflows -Common CI workflows and setup for go-openapi repos + + +[![Tests][test-badge]][test-url] [![Coverage][cov-badge]][cov-url] [![CI vuln scan][vuln-scan-badge]][vuln-scan-url] [![CodeQL][codeql-badge]][codeql-url] + + + +[![Release][release-badge]][release-url] [![Go Report Card][gocard-badge]][gocard-url] [![CodeFactor Grade][codefactor-badge]][codefactor-url] [![License][license-badge]][license-url] + + + +[![GoDoc][godoc-badge]][godoc-url] [![go version][goversion-badge]][goversion-url] ![Top language][top-badge] ![Commits since latest release][commits-badge] + +--- + +Common Continuous Integration (`CI`) workflows and setup for go-openapi repos. + +* shared github action workflows +* shared `dependabot` configuration (**BLOCKED**) +* shared `golangci-lint` configuration (**BLOCKED**) + +## Status + +Unreleased. + +Initial setup. Content may evolve with breaking changes. + +> NOTE: at this moment, it is difficult to share the dependabot and golangci-lint configurations, +> so these are not shared yet. + +## Basic usage + +## Motivation + +It took a while (well a something like 10 years...), but we eventually managed to align all checks, tests and +dependabot rules declared in the family of go-openapi repos. + +Now we'd like to be able to maintain, enrich and improve these checks without +worrying too much about the burden of replicating it about a dozen times. + +## Contemplated enhancements + +In no particular order: + +* [x] ui: enrich github actions UI with a job summary +* [x] doc: add markdown linting for docs +* [x] doc: add spellcheck for docs (and code?) +* [x] introduce config file specific checkout (markdownlint, spellcheck) +* [x] security: separate PR / issue comments as a trusted bot workflow, acting on request artifacts +* [ ] version common workflows, so we can limit the impact of a change +* [ ] build: verify that go.sum cache for tests works (should be enabled) +* [ ] share mono repo workflows (see github.com/go-openapi/swag/.github/workflows) +* [ ] lint: manage somehow to share golangci config (with local merge) +* [ ] dependencies: manage somehow to share / replicate dependabot config +* [ ] lint: golangci-lint: check valid PR comments etc +* [ ] lint: use non-blocking, scheduled, proactive full linting to check for + the impact of new linters, new go versions etc +* [ ] doc: (possibility) take over hugo & doc gen part from go-swagger +* [ ] (possibility) take over release part from go-swagger +* [ ] doc: produce hugo github page with all latest tagged versions + (incl. mono repo) +* [ ] add bot to filter PRs, issues +* [ ] check with github API that all repo settings (branch protection rules, etc) + are identical +* [ ] comment PRs and issues +* [ ] doc: checkout vale style-check guide (vale-action exists) +* [ ] doc: experiment LanguageTool for grammar checks ( -> a github action / docker image exists) +* [ ] doc: experiment LLM from github model, using embeddings ( -> +* [ ] issues: experiment LLM from github model, using embeddings ( -> show related issues) +* [ ] github pages w/ hugo (like go-swagger, experiment another theme and json data) + +## Change log + +See + +## Licensing + +This content ships under the [SPDX-License-Identifier: Apache-2.0](./LICENSE). + + + +## Other documentation + +* [All-time contributors](./CONTRIBUTORS.md) +* [Contributing guidelines](.github/CONTRIBUTING.md) +* [Maintainers documentation](docs/MAINTAINERS.md) +* [Code style](docs/STYLE.md) + + +[test-badge]: https://github.com/go-openapi/ci-workflows/actions/workflows/go-test.yml/badge.svg +[test-url]: https://github.com/go-openapi/ci-workflows/actions/workflows/go-test.yml +[cov-badge]: https://codecov.io/gh/go-openapi/ci-workflows/branch/master/graph/badge.svg +[cov-url]: https://codecov.io/gh/go-openapi/ci-workflows +[vuln-scan-badge]: https://github.com/go-openapi/ci-workflows/actions/workflows/scanner.yml/badge.svg +[vuln-scan-url]: https://github.com/go-openapi/ci-workflows/actions/workflows/scanner.yml +[codeql-badge]: https://github.com/go-openapi/ci-workflows/actions/workflows/codeql.yml/badge.svg +[codeql-url]: https://github.com/go-openapi/ci-workflows/actions/workflows/codeql.yml + +[release-badge]: https://badge.fury.io/gh/go-openapi%2Fci-workflowser.svg +[release-url]: https://badge.fury.io/gh/go-openapi%2Fci-workflowser +[gomod-badge]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fci-workflowser.svg +[gomod-url]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fci-workflowser + +[gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/ci-workflows +[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/ci-workflows +[codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/ci-workflows +[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/ci-workflows + +[doc-badge]: https://img.shields.io/badge/doc-site-blue?link=https%3A%2F%2Fgoswagger.io%2Fgo-openapi%2F +[doc-url]: https://goswagger.io/go-openapi +[godoc-badge]: https://pkg.go.dev/badge/github.com/go-openapi/ci-workflows +[godoc-url]: http://pkg.go.dev/github.com/go-openapi/ci-workflows +[slack-badge]: https://slackin.goswagger.io/badge.svg +[slack-url]: https://slackin.goswagger.io + +[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg +[license-url]: https://github.com/go-openapi/ci-workflows/?tab=Apache-2.0-1-ov-file#readme + +[goversion-badge]: https://img.shields.io/github/go-mod/go-version/go-openapi/ci-workflows +[goversion-url]: https://github.com/go-openapi/ci-workflows/blob/master/go.mod +[top-badge]: https://img.shields.io/github/languages/top/go-openapi/ci-workflows +[commits-badge]: https://img.shields.io/github/commits-since/go-openapi/ci-workflows/latest diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..2a7b6f0 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +This policy outlines the commitment and practices of the go-openapi maintainers regarding security. + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 0.22.x | :white_check_mark: | + +## Reporting a vulnerability + +If you become aware of a security vulnerability that affects the current repository, +please report it privately to the maintainers. + +Please follow the instructions provided by github to +[Privately report a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability). + +TL;DR: on Github, navigate to the project's "Security" tab then click on "Report a vulnerability". diff --git a/audit/README.md b/audit/README.md new file mode 100644 index 0000000..eb5eadf --- /dev/null +++ b/audit/README.md @@ -0,0 +1,3 @@ +# Audit + +Audit of github repos to check for inconsistencies. diff --git a/config-templates/.cliff.toml b/config-templates/.cliff.toml new file mode 100644 index 0000000..702629f --- /dev/null +++ b/config-templates/.cliff.toml @@ -0,0 +1,181 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration + +[changelog] +header = """ +""" + +footer = """ + +----- + +**[{{ remote.github.repo }}]({{ self::remote_url() }}) license terms** + +[![License][license-badge]][license-url] + +[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg +[license-url]: {{ self::remote_url() }}/?tab=Apache-2.0-1-ov-file#readme + +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} +""" + +body = """ +{%- if version %} +## [{{ version | trim_start_matches(pat="v") }}]({{ self::remote_url() }}/tree/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }} +{%- else %} +## [unreleased] +{%- endif %} +{%- if message %} + {%- raw %}\n{% endraw %} +{{ message }} + {%- raw %}\n{% endraw %} +{%- endif %} +{%- if version %} + {%- if previous.version %} + +**Full Changelog**: <{{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}> + {%- endif %} +{%- else %} + {%- raw %}\n{% endraw %} +{%- endif %} + +{%- if statistics %}{% if statistics.commit_count %} + {%- raw %}\n{% endraw %} +{{ statistics.commit_count }} commits in this release. + {%- raw %}\n{% endraw %} +{%- endif %}{% endif %} +----- + +{%- for group, commits in commits | group_by(attribute="group") %} + {%- raw %}\n{% endraw %} +### {{ group | upper_first }} + {%- raw %}\n{% endraw %} + {%- for commit in commits %} + {%- if commit.remote.pr_title %} + {%- set commit_message = commit.remote.pr_title %} + {%- else %} + {%- set commit_message = commit.message %} + {%- endif %} +* {{ commit_message | split(pat="\n") | first | trim }} + {%- if commit.remote.username %} +{%- raw %} {% endraw %}by [@{{ commit.remote.username }}](https://github.com/{{ commit.remote.username }}) + {%- endif %} + {%- if commit.remote.pr_number %} +{%- raw %} {% endraw %}in [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) + {%- endif %} +{%- raw %} {% endraw %}[...]({{ self::remote_url() }}/commit/{{ commit.id }}) + {%- endfor %} +{%- endfor %} + +{%- if github %} +{%- raw %}\n{% endraw -%} + {%- set all_contributors = github.contributors | length %} + {%- if github.contributors | filter(attribute="username", value="dependabot[bot]") | length < all_contributors %} +----- + +### People who contributed to this release + {% endif %} + {%- for contributor in github.contributors | filter(attribute="username") | sort(attribute="username") %} + {%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %} +* [@{{ contributor.username }}](https://github.com/{{ contributor.username }}) + {%- endif %} + {%- endfor %} + + {% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} +----- + {%- raw %}\n{% endraw %} + +### New Contributors + {%- endif %} + + {%- for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} + {%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %} +* @{{ contributor.username }} made their first contribution + {%- if contributor.pr_number %} + in [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \ + {%- endif %} + {%- endif %} + {%- endfor %} +{%- endif %} + +{%- raw %}\n{% endraw %} + +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} +""" +# Remove leading and trailing whitespaces from the changelog's body. +trim = true +# Render body even when there are no releases to process. +render_always = true +# An array of regex based postprocessors to modify the changelog. +postprocessors = [ + # Replace the placeholder with a URL. + #{ pattern = '', replace = "https://github.com/orhun/git-cliff" }, +] +# output file path +# output = "test.md" + +[git] +# Parse commits according to the conventional commits specification. +# See https://www.conventionalcommits.org +conventional_commits = false +# Exclude commits that do not match the conventional commits specification. +filter_unconventional = false +# Require all commits to be conventional. +# Takes precedence over filter_unconventional. +require_conventional = false +# Split commits on newlines, treating each line as an individual commit. +split_commits = false +# An array of regex based parsers to modify commit messages prior to further processing. +commit_preprocessors = [ + # Replace issue numbers with link templates to be updated in `changelog.postprocessors`. + #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, + # Check spelling of the commit message using https://github.com/crate-ci/typos. + # If the spelling is incorrect, it will be fixed automatically. + #{ pattern = '.*', replace_command = 'typos --write-changes -' } +] +# Prevent commits that are breaking from being excluded by commit parsers. +protect_breaking_commits = false +# An array of regex based parsers for extracting data from the commit message. +# Assigns commits to groups. +# Optionally sets the commit's scope and can decide to exclude commits from further processing. +commit_parsers = [ + { message = "^[Cc]hore\\([Rr]elease\\): prepare for", skip = true }, + { message = "(^[Mm]erge)|([Mm]erge conflict)", skip = true }, + { field = "author.name", pattern = "dependabot*", group = "Updates" }, + { message = "([Ss]ecurity)|([Vv]uln)", group = "Security" }, + { body = "(.*[Ss]ecurity)|([Vv]uln)", group = "Security" }, + { message = "([Cc]hore\\(lint\\))|(style)|(lint)|(codeql)|(golangci)", group = "Code quality" }, + { message = "(^[Dd]oc)|((?i)readme)|(badge)|(typo)|(documentation)", group = "Documentation" }, + { message = "(^[Ff]eat)|(^[Ee]nhancement)", group = "Implemented enhancements" }, + { message = "(^ci)|(\\(ci\\))|(fixup\\s+ci)|(fix\\s+ci)|(license)|(example)", group = "Miscellaneous tasks" }, + { message = "^test", group = "Testing" }, + { message = "(^fix)|(panic)", group = "Fixed bugs" }, + { message = "(^refact)|(rework)", group = "Refactor" }, + { message = "(^[Pp]erf)|(performance)", group = "Performance" }, + { message = "(^[Cc]hore)", group = "Miscellaneous tasks" }, + { message = "^[Rr]evert", group = "Reverted changes" }, + { message = "(upgrade.*?go)|(go\\s+version)", group = "Updates" }, + { message = ".*", group = "Other" }, +] +# Exclude commits that are not matched by any commit parser. +filter_commits = false +# An array of link parsers for extracting external references, and turning them into URLs, using regex. +link_parsers = [] +# Include only the tags that belong to the current branch. +use_branch_tags = false +# Order releases topologically instead of chronologically. +topo_order = false +# Order releases topologically instead of chronologically. +topo_order_commits = true +# Order of commits in each group/release within the changelog. +# Allowed values: newest, oldest +sort_commits = "newest" +# Process submodules commits +recurse_submodules = false + +#[remote.github] +#owner = "go-openapi" diff --git a/config-templates/.editorconfig b/config-templates/.editorconfig new file mode 100644 index 0000000..3152da6 --- /dev/null +++ b/config-templates/.editorconfig @@ -0,0 +1,26 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +# Set default charset +[*.{js,py,go,scala,rb,java,html,css,less,sass,md}] +charset = utf-8 + +# Tab indentation (no size specified) +[*.go] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 diff --git a/config-templates/.golangci.yml b/config-templates/.golangci.yml new file mode 100644 index 0000000..fdae591 --- /dev/null +++ b/config-templates/.golangci.yml @@ -0,0 +1,66 @@ +version: "2" +linters: + default: all + disable: + - depguard + - funlen + - godox + - exhaustruct + - nlreturn + - nonamedreturns + - noinlineerr + - paralleltest + - recvcheck + - testpackage + - tparallel + - varnamelen + - whitespace + - wrapcheck + - wsl + - wsl_v5 + settings: + dupl: + threshold: 200 + goconst: + min-len: 2 + min-occurrences: 3 + cyclop: + max-complexity: 20 + gocyclo: + min-complexity: 20 + exhaustive: + default-signifies-exhaustive: true + default-case-required: true + lll: + line-length: 180 + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + - gofumpt + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ +issues: + # Maximum issues count per one linter. + # Set to 0 to disable. + # Default: 50 + max-issues-per-linter: 0 + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 0 diff --git a/doc-templates/.github/CONTRIBUTING.md b/doc-templates/.github/CONTRIBUTING.md new file mode 100644 index 0000000..b02b363 --- /dev/null +++ b/doc-templates/.github/CONTRIBUTING.md @@ -0,0 +1,214 @@ +## Contribution Guidelines + +You'll find below general guidelines, which mostly correspond to standard practices for open sourced repositories. + +>**TL;DR** +> +> If you're already an experience go developer on github, then you should just feel at home with us +> and you may well skip the rest of this document. +> +> You'll essentially find the usual guideline for a go library project on github. + +These guidelines are general to all libraries published on github by the `go-openapi` organization. + +You'll find more detailed (or repo-specific) instructions in the [maintainer's docs](../docs). + +## How can I contribute? + +There are many ways in which you can contribute. Here are a few ideas: + + * Reporting Issues / Bugs + * Suggesting Improvements + * Code + * bug fixes and new features that are within the main project scope + * improving test coverage + * addressing code quality issues + * Documentation + * Art work that makes the project look great + +## Questions & issues + +### Asking questions + +You may inquire about anything about this library by reporting a "Question" issue on github. + +### Reporting issues + +Reporting a problem with our libraries _is_ a valuable contribution. + +You can do this on the github issues page of this repository. + +Please be as specific as possible when describing your issue. + +Whenever relevant, please provide information about your environment (go version, OS). + +Adding a code snippet to reproduce the issue is great, and as a big time saver for maintainers. + +### Triaging issues + +You can help triage issues which may include: + +* reproducing bug reports +* asking for important information, such as version numbers or reproduction instructions +* answering questions and sharing your insight in issue comments + +## Code contributions + +### Pull requests are always welcome + +We are always thrilled to receive pull requests, and we do our best to +process them as fast as possible. + +Not sure if that typo is worth a pull request? Do it! We will appreciate it. + +If your pull request is not accepted on the first try, don't be discouraged! +If there's a problem with the implementation, hopefully you received feedback on what to improve. + +If you have a lot of ideas or a lot of issues to solve, try to refrain a bit and post focused +pull requests. +Think that they must be reviewed by a maintainer and it is easy to lost track of things on big PRs. + +We're trying very hard to keep the go-openapi packages lean and focused. +These packages constitute a toolkit: it won't do everything for everybody out of the box, +but everybody can use it to do just about everything related to OpenAPI. + +This means that we might decide against incorporating a new feature. + +However, there might be a way to implement that feature *on top of* our libraries. + +### Environment + +You just need a `go` compiler to be installed. No special tools are needed to work with our libraries. + +The go compiler version required is always the old stable (latest minor go version - 1). + +If you're already used to work with `go` you should already have everything in place. + +Although not required, you'll be certainly more productive with a local installation of `golangci-lint`, +the meta-linter our CI uses. + +If you don't have it, you may install it like so: + +```sh +go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest +``` + +### Conventions + +#### Git flow + +Fork the repo and make changes to your fork in a feature branch. + +To submit a pull request, push your branch to your fork (e.g. `upstream` remote): +github will propose to open a pull request on the original repository. + +Typically you'd follow some common naming conventions: + +- if it's a bugfix branch, name it `fix/XXX-something`where XXX is the number of the + issue on github +- if it's a feature branch, create an enhancement issue to announce your + intentions, and name it `feature/XXX-something` where XXX is the number of the issue. + +> NOTE: we don't enforce naming conventions on branches: it's your fork after all. + +#### Tests + +Submit unit tests for your changes. + +Go has a great built-in test framework ; use it! + +Take a look at existing tests for inspiration, and run the full test suite on your branch +before submitting a pull request. + +Our CI measures test coverage and the test coverage of every patch. +Although not a blocking step - because there are so many special cases - +this is an indicator that maintainers consider when approving a PR. + +Please try your best to cover about 80% of your patch. + +#### Code style + +You may read our stance on code style [there](../docs/STYLE.md). + +#### Documentation + +Don't forget to update the documentation when creating or modifying features. + +Most documentation for this library is directly found in code as comments for godoc. + +The documentation for the go-openapi packages is published on the public go docs site: + + + +Check your documentation changes for clarity, concision, and correctness. + +If you want to assess the rendering of your changes when published to `pkg.go.dev`, you may +want to install the `pkgsite` tool proposed by `golang.org`. + +```sh +go install golang.org/x/pkgsite/cmd/pkgsite@latest +``` + +Then run on the repository folder: +```sh +pkgsite . +``` + +This wil run a godoc server locally where you may see the documentation generated from your local repository. + +#### Commit messages + +Pull requests descriptions should be as clear as possible and include a +reference to all the issues that they address. + +Pull requests must not contain commits from other users or branches. + +Commit messages are not required to follow the "conventional commit" rule, but it's certainly a good +thing to follow this guidelinea (e.g. "fix: blah blah", "ci: did this", "feat: did that" ...). + +The title in your commit message is used directly to produce our release notes: try to keep them neat. + +The commit message body should detail your changes. + +If an issue should be closed by a commit, please add this reference in the commit body: + +``` +* fixes #{issue number} +``` + +#### Code review + +Code review comments may be added to your pull request. + +Discuss, then make the suggested modifications and push additional commits to your feature branch. + +Be sure to post a comment after pushing. The new commits will show up in the pull +request automatically, but the reviewers will not be notified unless you comment. + +Before the pull request is merged, +**make sure that you squash your commits into logical units of work** +using `git rebase -i` and `git push -f`. + +After every commit the test suite should be passing. + +Include documentation changes in the same commit so that a revert would remove all traces of the feature or fix. + +#### Sign your work + +The sign-off is a simple line at the end of your commit message, +which certifies that you wrote it or otherwise have the right to +pass it on as an open-source patch. + +We require the simple DCO below with an email signing your commit. +PGP-signed commit are greatly appreciated but not required. + +The rules are pretty simple: + +* read our [DCO](./DCO.md) (from [developercertificate.org](http://developercertificate.org/)) +* if you agree with these terms, then you just add a line to every git commit message + + Signed-off-by: Joe Smith + +using your real name (sorry, no pseudonyms or anonymous contributions.) + +You can add the sign off when creating the git commit via `git commit -s`. diff --git a/doc-templates/.github/DCO.md b/doc-templates/.github/DCO.md new file mode 100644 index 0000000..e168dc4 --- /dev/null +++ b/doc-templates/.github/DCO.md @@ -0,0 +1,40 @@ + # Developer's Certificate of Origin + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` diff --git a/doc-templates/CODE_OF_CONDUCT.md b/doc-templates/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..9322b06 --- /dev/null +++ b/doc-templates/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at ivan+abuse@flanders.co.nz. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/doc-templates/README.md b/doc-templates/README.md new file mode 100644 index 0000000..94d45e7 --- /dev/null +++ b/doc-templates/README.md @@ -0,0 +1,91 @@ +# {{ .Repo }} + + +[![Tests][test-badge]][test-url] [![Coverage][cov-badge]][cov-url] [![CI vuln scan][vuln-scan-badge]][vuln-scan-url] [![CodeQL][codeql-badge]][codeql-url] + + + +[![Release][release-badge]][release-url] [![Go Report Card][gocard-badge]][gocard-url] [![CodeFactor Grade][codefactor-badge]][codefactor-url] [![License][license-badge]][license-url] + + + +[![GoDoc][godoc-badge]][godoc-url] [![go version][goversion-badge]][goversion-url] ![Top language][top-badge] ![Commits since latest release][commits-badge] + +--- + +{{ .Title }} + +## Status + +API is stable. + +## Import this library in your project + +```cmd +go get github.com/{{ .Owner }}/{{ .Repo }} +``` + +## Basic usage + +## Change log + +See + + + +## Licensing + +This library ships under the [SPDX-License-Identifier: Apache-2.0](./LICENSE). + + + + + +## Other documentation + +* [All-time contributors](./CONTRIBUTORS.md) +* [Contributing guidelines](.github/CONTRIBUTING.md) +* [Maintainers documentation](docs/MAINTAINERS.md) +* [Code style](docs/STYLE.md) + + +[test-badge]: https://github.com/{{ .Owner }}/{{ .Repo }}/actions/workflows/go-test.yml/badge.svg +[test-url]: https://github.com/{{ .Owner }}/{{ .Repo }}/actions/workflows/go-test.yml +[cov-badge]: https://codecov.io/gh/{{ .Owner }}/{{ .Repo }}/branch/master/graph/badge.svg +[cov-url]: https://codecov.io/gh/{{ .Owner }}/{{ .Repo }} +[vuln-scan-badge]: https://github.com/{{ .Owner }}/{{ .Repo }}/actions/workflows/scanner.yml/badge.svg +[vuln-scan-url]: https://github.com/{{ .Owner }}/{{ .Repo }}/actions/workflows/scanner.yml +[codeql-badge]: https://github.com/{{ .Owner }}/{{ .Repo }}/actions/workflows/codeql.yml/badge.svg +[codeql-url]: https://github.com/{{ .Owner }}/{{ .Repo }}/actions/workflows/codeql.yml + +[release-badge]: https://badge.fury.io/gh/{{ .Owner }}%2F{{ .Repo }}.svg +[release-url]: https://badge.fury.io/gh/{{ .Owner }}%2F{{ .Repo }} +[gomod-badge]: https://badge.fury.io/go/github.com%2F{{ .Owner }}%2F{{ .Repo }}.svg +[gomod-url]: https://badge.fury.io/go/github.com%2F{{ .Owner }}%2F{{ .Repo }} + +[gocard-badge]: https://goreportcard.com/badge/github.com/{{ .Owner }}/{{ .Repo }} +[gocard-url]: https://goreportcard.com/report/github.com/{{ .Owner }}/{{ .Repo }} +[codefactor-badge]: https://img.shields.io/codefactor/grade/github/{{ .Owner }}/{{ .Repo }} +[codefactor-url]: https://www.codefactor.io/repository/github/{{ .Owner }}/{{ .Repo }} + +[doc-badge]: https://img.shields.io/badge/doc-site-blue?link=https%3A%2F%2Fgoswagger.io%2F{{ .Owner }}%2F +[doc-url]: https://goswagger.io/{{ .Owner }} +[godoc-badge]: https://pkg.go.dev/badge/github.com/{{ .Owner }}/{{ .Repo }} +[godoc-url]: http://pkg.go.dev/github.com/{{ .Owner }}/{{ .Repo }} +[slack-badge]: https://slackin.goswagger.io/badge.svg +[slack-url]: https://slackin.goswagger.io + +[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg +[license-url]: https://github.com/{{ .Owner }}/{{ .Repo }}/?tab=Apache-2.0-1-ov-file#readme + +[goversion-badge]: https://img.shields.io/github/go-mod/go-version/{{ .Owner }}/{{ .Repo }} +[goversion-url]: https://github.com/{{ .Owner }}/{{ .Repo }}/blob/master/go.mod +[top-badge]: https://img.shields.io/github/languages/top/{{ .Owner }}/{{ .Repo }} +[commits-badge]: https://img.shields.io/github/commits-since/{{ .Owner }}/{{ .Repo }}/latest diff --git a/doc-templates/SECURITY.md b/doc-templates/SECURITY.md new file mode 100644 index 0000000..2a7b6f0 --- /dev/null +++ b/doc-templates/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +This policy outlines the commitment and practices of the go-openapi maintainers regarding security. + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 0.22.x | :white_check_mark: | + +## Reporting a vulnerability + +If you become aware of a security vulnerability that affects the current repository, +please report it privately to the maintainers. + +Please follow the instructions provided by github to +[Privately report a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability). + +TL;DR: on Github, navigate to the project's "Security" tab then click on "Report a vulnerability". diff --git a/doc-templates/docs/MAINTAINERS.md b/doc-templates/docs/MAINTAINERS.md new file mode 100644 index 0000000..f83496b --- /dev/null +++ b/doc-templates/docs/MAINTAINERS.md @@ -0,0 +1,157 @@ +# Maintainer's guide + +## Repo structure + +Single go module. + +> **NOTE** +> +> Some `{{ .Owner }}` repos are mono-repos with multiple modules, +> with adapted CI workflows. + +## Repo configuration + +* default branch: master +* protected branches: master +* branch protection rules: + * require pull requests and approval + * required status checks: + - DCO (simple email sign-off) + - Lint + - tests completed +* auto-merge enabled (used for dependabot updates) + +## Continuous Integration + +### Code Quality checks + +* meta-linter: golangci-lint +* linter config: [`.golangci.yml`](../.golangci.yml) (see our [posture](./STYLE.md) on linters) + +* Code quality assessment: [CodeFactor](https://www.codefactor.io/dashboard) +* Code quality badges + * go report card: + * CodeFactor: + +> **NOTES** +> +> codefactor inherits roles from github. There is no need to create a dedicated account. +> +> The codefactor app is installed at the organization level (`github.com/{{ .Owner }}`). +> +> There is no special token to setup in github for CI usage. + +### Testing + +* Test reports + * Uploaded to codecov: +* Test coverage reports + * Uploaded to codecov: + +* Fuzz testing + * Fuzz tests are handled separately by CI and may reuse a cached version of the fuzzing corpus. + At this moment, cache may not be shared between feature branches or feature branch and master. + The minimized corpus produced on failure is uploaded as an artifact and should be added manually + to `testdata/fuzz/...`. + +Coverage threshold status is informative and not blocking. +This is because the thresholds are difficult to tune and codecov oftentimes reports false negatives +or may fail to upload coverage. + +All tests use our fork of `stretchr/testify`: `github.com/{{ .Owner }}/testify`. +This allows for minimal test dependencies. + +> **NOTES** +> +> codecov inherits roles from github. There is no need to create a dedicated account. +> However, there is only 1 maintainer allowed to be the admin of the organization on codecov +> with their free plan. +> +> The codecov app is installed at the organization level (`github.com/{{ .Owner }}`). +> +> There is no special token to setup in github for CI usage. +> A organization-level token used to upload coverage and test reports is managed at codecov: +> no setup is required on github. + +### Automated updates + +* dependabot + * configuration: [`dependabot.yaml`](../.github/dependabot.yaml) + + Principle: + + * codecov applies updates and security patches to the github-actions and golang ecosystems. + * all updates from "trusted" dependencies (github actions, golang.org packages, {{ .Owner }} packages + are auto-merged if they successfully pass CI. + +* go version udpates + + Principle: + + * we support the 2 latest minor versions of the go compiler (`stable`, `oldstable`) + * `go.mod` should be updated (manually) whenever there is a new go minor release + (e.g. every 6 months). + +* contributors + * a [`CONTRIBUTORS.md`](../CONTRIBUTORS.md) file is updated weekly, with all-time contributors to the repository + * the `github-actions[bot]` posts a pull request to do that automatically + * at this moment, this pull request is not auto-approved/auto-merged (bot cannot approve its own PRs) + +### Vulnerability scanners + +There are 3 complementary scanners - obviously, there is some overlap, but each has a different focus. + +* github `CodeQL` +* `trivy` +* `govulnscan` + +None of these tools require an additional account or token. + +Github CodeQL configuration is set to "Advanced", so we may collect a CI status for this check (e.g. for badges). + +Scanners run on every commit to master and at least once a week. + +Reports are centralized in github security reports for code scanning tools. + +## Releases + +The release process is minimalist: + +* push a semver tag (i.e v{major}.{minor}.{patch}) to the master branch. +* the CI handles this to generate a github release with release notes + +* release notes generator: git-cliff +* configuration: [`cliff.toml`](../.cliff.toml) + +Tags are preferably PGP-signed. + +The tag message introduces the release notes (e.g. a summary of this release). + +The release notes generator does not assume that commits are necessarily "conventional commits". + +## Other files + +Standard documentation: + +* [`CONTRIBUTING.md`](../.github/CONTRIBUTING.md) guidelines +* [`DCO.md`](../.github/DCO.md) terms for first-time contributors to read +* [`CODE_OF_CONDUCT.md`](../CODE_OF_CONDUCT.md) +* [`SECURIY.md`](../SECURITY.md) policy: how to report vulnerabilities privately +* [`LICENSE`](../LICENSE) terms +* [`NOTICE`](../NOTICE) on supplementary license terms (original authors, copied code etc) + +Reference documentation (released): + +* [godoc](https://pkg.go/dev/{{ .Owner }}/{{ .Repo }}) + +## TODOs & other ideas + +A few things remain ahead to ease a bit a maintainer's job: + +* [ ] reuse CI workflows (e.g. in `github.com/{{ .Owner }}/workflows`) +* [ ] reusable actions with custom tools pinned (e.g. in `github.com/{{ .Owner }}/gh-actions`) +* [ ] open-source license checks +* [ ] auto-merge for CONTRIBUTORS.md (requires a github app to produce tokens) +* [ ] more automated code renovation / relinting work (possibly built with CLAUDE) +* [ ] organization-level documentation web site +* ... diff --git a/doc-templates/docs/STYLE.md b/doc-templates/docs/STYLE.md new file mode 100644 index 0000000..a82dad4 --- /dev/null +++ b/doc-templates/docs/STYLE.md @@ -0,0 +1,83 @@ +# Coding style at `go-openapi` + +> **TL;DR** +> +> Let's be honest: at `go-openapi` and `go-swagger` we've never been super-strict on code style etc. +> +> But perhaps now (2025) is the time to adopt a different stance. + +Even though our repos have been early adopters of `golangci-lint` years ago +(we used some other metalinter before), our decade-old codebase is only realigned to new rules from time to time. + +Now go-openapi and go-swagger make up a really large codebase, which is taxing to maintain and keep afloat. + +Code quality and the harmonization of rules have thus become things that we need now. + +## Meta-linter + +Universally formatted go code promotes ease of writing, reading, and maintenance. + +You should run `golangci-lint run` before committing your changes. + +Many editors have plugins that do that automatically. + +> We use the `golangci-lint` meta-linter. The configuration lies in [`.golangci.yml`](../.golangci.yml). +> You may read for additional reference. + +## Linting rules posture + +Thanks to go's original design, we developers don't have to waste much time arguing about code figures of style. + +However, the number of available linters has been growing to the point that we need to pick a choice. + +We enable all linters published by `golangci-lint` by default, then disable a few ones. + +Here are the reasons why they are disabled (update: Nov. 2025, `golangci-lint v2.6.1`): + +```yaml + disable: + - depguard # we don't want to configure rules to constrain import. That's the reviewer's job + - exhaustruct # we don't want to configure regexp's to check type name. That's the reviewer's job + - funlen # we accept cognitive complexity as a meaningful metric, but function length is relevant + - godox # we don't see any value in forbidding TODO's etc in code + - nlreturn # we usually apply this "blank line" rule to make code less compact. We just don't want to enforce it + - nonamedreturns # we don't see any valid reason why we couldn't used named returns + - noinlineerr # there is no value added forbidding inlined err + - paralleltest # we like parallel tests. We just don't want them to be enforced everywhere + - recvcheck # we like the idea of having pointer and non-pointer receivers + - testpackage # we like test packages. We just don't want them to be enforced everywhere + - tparallel # see paralleltest + - varnamelen # sometimes, we like short variables. The linter doesn't catch cases when a short name is good + - whitespace # no added value + - wrapcheck # although there is some sense with this linter's general idea, it produces too much noise + - wsl # no added value. Noise + - wsl_v5 # no added value. Noise +``` + +As you may see, we agree with the objectives of most linters, at least the principle they are supposed to enforce. +But all linters do not support fine-grained tuning to tolerate some cases and not some others. + +When this is possible, we enable linters with relaxed constraints: + +```yaml + settings: + dupl: + threshold: 200 # in a older code base such as ours, we have to be tolerant with a little redundancy + # Hopefully, we'll be able to gradually get rid of those. + goconst: + min-len: 2 + min-occurrences: 3 + cyclop: + max-complexity: 20 # the default is too low for most of our functions. 20 is a nicer trade-off + gocyclo: + min-complexity: 20 + exhaustive: # when using default in switch, this should be good enough + default-signifies-exhaustive: true + default-case-required: true + lll: + line-length: 180 # we just want to avoid extremely long lines. + # It is no big deal if a line or two don't fit on your terminal. +``` + +Final note: since we have switched to a forked version of `stretchr/testify`, +we no longer benefit from the great `testifylint` linter for tests. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..9cc9f00 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,5 @@ +# Share workflows documentation + +** DRAFT ** + +Documentation check and generation tools. diff --git a/experimental/go-test-monorepo.yml b/experimental/go-test-monorepo.yml new file mode 100644 index 0000000..324fc12 --- /dev/null +++ b/experimental/go-test-monorepo.yml @@ -0,0 +1,156 @@ +name: go test + +permissions: + contents: read + pull-requests: read + +on: + workflow_call: + +defaults: + run: + shell: bash + +jobs: + lint: + name: Go lint mono-repo + runs-on: ubuntu-latest + steps: + - + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + fetch-depth: 0 + - + uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + with: + go-version: stable + check-latest: true + cache: true + cache-dependency-path: '**/go.sum' + - + name: Detect if mono-repo + id: detect-monorepo + run: | + count_modules=$(go list -m|wc -l) + if [[ "${count_module}" -gt 1 ]] ; then + echo "is_monorepo=true" >> "${GITHUB_OUTPUT}" + exit + fi + echo "is_monorepo=false" >> "${GITHUB_OUTPUT}" + - + name: Install golangci-lint + if: ${{ steps.detect-monorepo.outputs == 'true' }} + uses: golangci/golangci-lint-action@e7fa5ac41e1cf5b7d48e45e42232ce7ada589601 # v9.1.0 + with: + version: latest + skip-cache: true + install-only: true + - + name: Lint multiple modules + if: ${{ steps.detect-monorepo.outputs == 'true' }} + # golangci-lint doesn't support go.work to lint multiple modules in one single pass + # TODO: golangci-action v9.1+ has a built-in mono repo detection setup. + run: | + set -euxo pipefail + + git fetch origin master + git show --no-patch --oneline origin/master + + while read -r module_location ; do + pushd "${module_location}" + golangci-lint run --new-from-rev origin/master + popd + done < <(go list -f '{{.Dir}}' -m) + + test: + name: Unit tests mono-repo + needs: [ lint ] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + go_version: ['oldstable', 'stable' ] + steps: + - + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - + uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + with: + go-version: '${{ matrix.go_version }}' + check-latest: true + cache: true + cache-dependency-path: '**/go.sum' + - + name: Install gotestsum + uses: go-openapi/gh-actions/install/gotestsum@2c8f8152814933c4cead92a51558699238ee9565 + - + name: Run unit tests on all modules in this repo + run: | + # when go1.25 becomes the oldstable, we may replace this bash with "go test work" + declare -a ALL_MODULES + BASH_MAJOR=$(echo "${BASH_VERSION}"|cut -d'.' -f1) + if [[ "${BASH_MAJOR}" -ge 4 ]] ; then + mapfile ALL_MODULES < <(go list -f '{{.Dir}}/...' -m) + else + # for older bash versions, e.g. on macOS runner. This fallback will eventually disappear. + while read -r line ; do + ALL_MODULES+=("${line}") + done < <(go list -f '{{.Dir}}/...' -m) + fi + echo "::notice title=Modules found::${ALL_MODULES[@]}" + + # with go.work file enabled, go test recognizes sub-modules and collects all packages to be covered + # without specifying -coverpkg. + gotestsum \ + --jsonfile 'unit.report.${{ matrix.os }}-${{ matrix.go }}.json' \ + -- \ + -race \ + -p 2 \ + -count 1 \ + -timeout=20m \ + -coverprofile='unit.coverage.${{ matrix.os }}-${{ matrix.go }}.out' \ + -covermode=atomic \ + ${ALL_MODULES[@]} + - + name: Upload coverage artifacts + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + # *.coverage.* pattern is automatically detected by codecov + path: '**/*.coverage.*.out' + name: 'unit.coverage.${{ matrix.os }}-${{ matrix.go }}' + retention-days: 1 + - + name: Upload test report artifacts + # upload report even if test fail. BTW, this is when they are valuable. + if: ${{ !cancelled() }} + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + path: '**/unit.report.*.json' + name: 'unit.report.${{ matrix.os }}-${{ matrix.go }}' + retention-days: 1 + + fuzz-test: + uses: ./.github/workflows/fuzz-test.yml + + test-complete: + # description: | + # Be explicit about all tests being passed. This allows for setting up only a few status checks on PRs. + name: tests completed + needs: [test] + runs-on: ubuntu-latest + steps: + - + name: Tests completed + run: | + echo "::notice title=Success:All tests passed" + + collect-coverage: + needs: [test-complete] + if: ${{ !cancelled() && needs.test-complete.result == 'success' }} + uses: ./.github/workflows/collect-coverage.yml + + collect-reports: + name: collect test reports + needs: [test] + if: ${{ !cancelled() }} + uses: ./.github/workflows/collect-reports.yml diff --git a/experimental/local-doc-update.yml b/experimental/local-doc-update.yml new file mode 100644 index 0000000..6e507c0 --- /dev/null +++ b/experimental/local-doc-update.yml @@ -0,0 +1,16 @@ +name: doc update check +# description: This workflow mimics how a go-openapi repo would typically invoke the common workflow. + +on: + pull_request_target: + paths: + - '**/*.md' + +permissions: + pull-requests: write + contents: read + +jobs: + test: + uses: ./.github/workflows/doc-update.yml + secrets: inherit diff --git a/experimental/local-monorepo-go-test.yml b/experimental/local-monorepo-go-test.yml new file mode 100644 index 0000000..5cfdcd8 --- /dev/null +++ b/experimental/local-monorepo-go-test.yml @@ -0,0 +1,19 @@ +name: go test + +permissions: + contents: read + pull-requests: read + +on: + push: + tags: + - v* + branches: + - master + + pull_request: + +jobs: + go-monorepo-test: + uses: ./.github/workflows/monorepo-go-test.yml + secrets: inherit diff --git a/experimental/markdown-lint.yml b/experimental/markdown-lint.yml new file mode 100644 index 0000000..33fe637 --- /dev/null +++ b/experimental/markdown-lint.yml @@ -0,0 +1,272 @@ +name: doc update check +# description: | +# This workflow is designed to help improve documentation quality. +# +# It proceeds with markdown linting and checks spelling in markdown files every time a markdown file is changed +# in a pull request. +# +# > NOTE: this workflow is only concerned with markdown documentation, not documentation in code (e.g. godoc). +# +# ## Usage of github action features +# +# * shared workflow +# * temporary token exchange using github app to elevate privileges within a trusted workflow +# * job summary (on actions UI) +# +# ## Limitations of github action features +# +# * [ ] did not find a way yet to retrieve the repo repo specified by the caller of this workflow. +# Had to resort to `master` to retrieve extra config files. +# +# ## TODO +# +# * [ ] should make an adapted version that runs on schedule and analyzes all markdown. +# * [ ] in this case, instead of commenting pull requests, it should raise issues. +# * [ ] should be able to retrieve config files and dictionary from the called ref, not master. +# * [ ] should be able to merge config files and dictionary with local definitions on target repo. +# * [ ] should be able to work on diff +# * [ ] should format the output of spellcheck report + +on: + workflow_call: + +permissions: + contents: read + +env: + artifacts_dir: artifacts + + markdown_comment_title: Markdown linter + markdown_config: '.github/.markdownlint.yml' + markdown_artifact: markdown_comment.txt + +jobs: + markdown-changed: + uses: ./.github/workflows/markdown-changed.yml + + markdown-lint: + needs: markdown-changed + if: ${{ needs.markdown-changed.outputs.proceed == 'true' }} + runs-on: ubuntu-latest + env: + lintreport: './markdownlint-report.txt' + outputs: + proceed: ${{ steps.report-exists.outputs.proceed }} + congrats: ${{ steps.congrats.outputs.proceed }} + report: ${{ steps.report-exists.outputs.report }} + steps: + - + name: Originating repo checkout (e.g. public fork) + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + ref: ${{ github.event.pull_request.head.sha }} + - + name: Checkout markdown config + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + repository: go-openapi/ci-workflows + ref: master # TODO: retrieve workflow ref + sparse-checkout: | + ${{ env.markdown_config }} + sparse-checkout-cone-mode: false + path: ci-tools + - + name: Copy markdown config + # TODO: merge with local if present + run: | + cp ci-tools/"${{ env.markdown_config}}" "${{ env.markdown_config }}" + - + name: Run markdown linter + id: markdownlint + uses: DavidAnson/markdownlint-cli2-action@v21 + continue-on-error: true + with: + config: ./${{ env.markdown_config }} + globs: '${{ needs.markdown-changed.outputs.all_changed_files }}' + separator: ',' + output: '${{ env.lintreport }}' + # fix: true + - + name: Comment on success + if: ${{ steps.markdownlint.outcome == 'success' }} + id: congrats + run: | + echo "::notice:: no linting issue with changed markdown" + echo "proceed=true" >> $GITHUB_OUTPUT + - + name: Comment on markdown lint complaining + if: ${{ steps.markdownlint.outcome != 'success' && hashFiles(env.lintreport) != '' }} + id: report-exists + run: | + echo 'report<> $GITHUB_OUTPUT + cat ${{ env.lintreport }}|sed -e '$a\' >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + + if [[ "$(cat ${{ env.lintreport }}|wc -l)" != "0" ]] ; then + echo "proceed=true" >> $GITHUB_OUTPUT + echo "::notice::Detected some linting issues with changed markdown" + cat ${{ env.lintreport }}|sed -e '$a\' + else + echo "proceed=false" >> $GITHUB_OUTPUT + echo "::notice::No linting issues with changed markdown" + fi + - + name: Other linter errors + if: ${{ steps.markdownlint.outcome != 'success' && hashFiles(env.lintreport) == '' }} + run: | + echo "::error::markdown linter encountered an error" + exit 1 + # TODO + - + name: Parse markdownlint JSON report + run: | + # TODO + - + name: Prepare PR comment + run: | + # TODO + - + name: Upload PR comment as artifact + run: | + # TODO + +# pr-markdown-congrats: +# needs: markdown-lint +# if: ${{ needs.markdown-lint.outputs.congrats == 'true' }} +# runs-on: ubuntu-latest +# outputs: +# run_id: ${{ steps.notify_markdown_congrats.outputs.run_id }} +# target_repo: ${{ steps.notify_markdown_congrats.outputs.target_repo }} +# pr_number: ${{ steps.notify_markdown_congrats.outputs.pr_number }} +# pr_sha: ${{ steps.notify_markdown_congrats.outputs.pr_sha }} +# artifact_name: ${{ steps.notify_markdown_congrats.outputs.artifact_name }} +# comment_title: ${{ steps.notify_markdown_congrats.outputs.comment_title }} +# reactions: ${{ steps.notify_markdown_congrats.outputs.reactions }} +# steps: +# - +# name: Congrats +# id: notify_markdown_congrats +# run: | +# mkdir -p "${{ env.artifacts_dir }}" +# read -d '' MSG< "${{ env.artifacts_dir}}/${{ env.markdown_artifact }}" +# +# echo "### Your changes to markdown docs look good. 👍" >> $GITHUB_STEP_SUMMARY +# echo "" >> $GITHUB_STEP_SUMMARY +# echo "> â„šī¸ INFO: we use [avtodev/markdown-lint action](https://github.com/avto-dev/markdown-lint)" >> $GITHUB_STEP_SUMMARY +# +# echo "run_id=${{ github.run_id }}" >> "$GITHUB_OUTPUT" +# echo "target_repo=${{ github.repository }}" >> "$GITHUB_OUTPUT" +# echo "pr_number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" +# echo "pr_sha=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT" +# echo "artifact_name=${{ env.markdown_artifact }}" >> "$GITHUB_OUTPUT" +# echo "comment_title=${{ env.markdown_comment_title }}" >> "$GITHUB_OUTPUT" +# echo "reactions=hooray" >> "$GITHUB_OUTPUT" +# - +# name: Upload comment as artifact +# uses: actions/upload-artifact@v4 +# with: +# path: ${{ env.artifacts_dir }}/${{ env.markdown_artifact }} +# name: ${{ env.markdown_artifact }} +# +# pr-comment-markdown-congrats: +# name: Create or update congrats comment +# needs: pr-markdown-congrats +# uses: go-openapi/ci-workflows/.github/workflows/pr-comment.yml@master # <- TODO: caller's ref +# secrets: inherit +# with: +# run_id: ${{ needs.pr-markdown-congrats.outputs.run_id }} +# target_repo: ${{ needs.pr-markdown-congrats.outputs.target_repo }} +# pr_number: ${{ needs.pr-markdown-congrats.outputs.pr_number }} +# pr_sha: ${{ needs.pr-markdown-congrats.outputs.pr_sha }} +# artifact_name: ${{ needs.pr-markdown-congrats.outputs.artifact_name }} +# comment_title: ${{ needs.pr-markdown-congrats.outputs.comment_title }} +# reactions: ${{ needs.pr-markdown-congrats.outputs.reactions }} +# +# pr-markdown-report: +# needs: markdown-lint +# if: ${{ needs.markdown-lint.outputs.proceed == 'true' }} +# outputs: +# run_id: ${{ steps.notify_markdown_report.outputs.run_id }} +# target_repo: ${{ steps.notify_markdown_report.outputs.target_repo }} +# pr_number: ${{ steps.notify_markdown_report.outputs.pr_number }} +# pr_sha: ${{ steps.notify_markdown_report.outputs.pr_sha }} +# artifact_name: ${{ steps.notify_markdown_report.outputs.artifact_name }} +# comment_title: ${{ steps.notify_markdown_report.outputs.comment_title }} +# reactions: ${{ steps.notify_markdown_report.outputs.reactions }} +# runs-on: ubuntu-latest +# steps: +# - +# name: Format PR comment +# id: comment_formatter +# uses: skills/action-text-variables@v3 +# with: +# template-vars: > +# { +# "text": ${{ toJSON(needs.markdown-lint.outputs.report) }} +# } +# template-text: | +# ### ${{ env.markdown_comment_title }} +# +# Some markdown linting issues were detected in the modified .md files. âš ī¸ +# +# Perhaps they were already there before your changes. +# +# This check is advisory only and not blocking. Please help us adopt a nice markdown style. +# +# Markdown formatting rules are documented [here](https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md). +#
+# +# ``` +# {{ text }} +# ``` +# - +# name: Slap on the wrist +# env: +# OUTPUT: "${{ steps.comment_formatter.outputs.updated-text }}" +# run: | +# mkdir -p "${{ env.artifacts_dir }}" +# printenv OUTPUT > "${{ env.artifacts_dir}}/${{ env.markdown_artifact }}" +# +# echo "### Changed markdown docs need some formatting. âš ī¸" >> $GITHUB_STEP_SUMMARY +# echo "" >> $GITHUB_STEP_SUMMARY +# echo "> â„šī¸ INFO: we use [avtodev/markdown-lint action](https://github.com/avto-dev/markdown-lint)" >> $GITHUB_STEP_SUMMARY +# - +# name: Upload comment as artifact +# uses: actions/upload-artifact@v4 +# with: +# path: ${{ env.artifacts_dir }}/${{ env.markdown_artifact }} +# name: ${{ env.markdown_artifact }} +# - +# name: Notify +# id: notify_markdown_report +# run: | +# echo "::notice::Commented pull request ${{ github.event.pull_request.number }}" +# +# echo "run_id=${{ github.run_id }}" >> "$GITHUB_OUTPUT" +# echo "target_repo=${{ github.repository }}" >> "$GITHUB_OUTPUT" +# echo "pr_number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" +# echo "pr_sha=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT" +# echo "artifact_name=${{ env.markdown_artifact }}" >> "$GITHUB_OUTPUT" +# echo "comment_title=${{ env.markdown_comment_title }}" >> "$GITHUB_OUTPUT" +# echo "reactions=confused" >> "$GITHUB_OUTPUT" +# +# pr-comment-markdown-report: +# name: Create or update comment with report +# needs: pr-markdown-report +# uses: go-openapi/ci-workflows/.github/workflows/pr-comment.yml@master # <- TODO: caller's ref +# secrets: inherit +# with: +# run_id: ${{ needs.pr-markdown-report.outputs.run_id }} +# target_repo: ${{ needs.pr-markdown-report.outputs.target_repo }} +# pr_number: ${{ needs.pr-markdown-report.outputs.pr_number }} +# pr_sha: ${{ needs.pr-markdown-report.outputs.pr_sha }} +# artifact_name: ${{ needs.pr-markdown-report.outputs.artifact_name }} +# comment_title: ${{ needs.pr-markdown-report.outputs.comment_title }} +# reactions: ${{ needs.pr-markdown-report.outputs.reactions }} diff --git a/experimental/markdown-spellcheck.yml b/experimental/markdown-spellcheck.yml new file mode 100644 index 0000000..c26f375 --- /dev/null +++ b/experimental/markdown-spellcheck.yml @@ -0,0 +1,270 @@ +name: Markdown spellcheck + +on: + workflow_call: + +permissions: + contents: read + +env: + artifacts_dir: artifacts + + spellcheck_comment_title: Markdown spelling check + spellcheck_config: .github/.spellcheck.yml + spellcheck_dict: .github/.wordlist.txt + spellcheck_artifact: spellcheck_comment.txt + +jobs: + markdown-changed: + uses: ./.github/workflows/markdown-changed.yml + + markdown-spellcheck: + needs: markdown-changed + if: ${{ needs.markdown-changed.outputs.proceed == 'true' }} + runs-on: ubuntu-latest + env: + spellcheckreport: './spellcheck-report.txt' + outputs: + proceed: ${{ steps.report-exists.outputs.proceed }} + congrats: ${{ steps.congrats.outputs.proceed }} + report: ${{ steps.report-exists.outputs.report }} + steps: + - + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + ref: ${{ github.event.pull_request.head.sha }} + - + name: Checkout spellcheck config + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + repository: go-openapi/ci-workflows + ref: master # TODO: retrieve workflow ref + sparse-checkout: | + ${{ env.spellcheck_config }} + ${{ env.spellcheck_dict }} + sparse-checkout-cone-mode: false + path: ci-tools + - + name: Copy spellcheck config + # TODO: merge with local if present + run: | + cp ci-tools/${{ env.spellcheck_config }} ${{ env.spellcheck_config }} + cp ci-tools/${{ env.spellcheck_dict }} ${{ env.spellcheck_dict }} + - + name: Spellcheck + id: spellcheck + uses: crate-ci/typos@master + continue-on-error: true + with: + config_path: ${{ env.spellcheck_config }} + source_files: '${{ needs.markdown-changed.outputs.all_changed_files }}' + task_name: markdown + output_file: ${{ env.spellcheckreport }} + # TODO + - + name: Parse markdownlint JSON report + run: | + # TODO + - + name: Prepare PR comment + run: | + # TODO + - + name: Upload PR comment as artifact + run: | + # TODO +# - +# name: Comment on success +# if: ${{ steps.spellcheck.outcome == 'success' }} +# id: congrats +# run: | +# echo "::notice:: no spelling issue with changed markdown" +# echo "proceed=true" >> $GITHUB_OUTPUT +# +# echo "### Your changes to markdown docs show impeccable spelling. 👍" >> $GITHUB_STEP_SUMMARY +# echo "" >> $GITHUB_STEP_SUMMARY +# echo "> â„šī¸ INFO: we use [rojopolis/spellcheck-github-actions](https://github.com/rojopolis/spellcheck-github-actions)" >> $GITHUB_STEP_SUMMARY +# - +# name: Comment on spellcheck complaining +# if: ${{ steps.spellcheck.outcome != 'success' && hashFiles(env.spellcheckreport) != '' }} +# id: report-exists +# run: | +# echo 'report<> $GITHUB_OUTPUT +# cat ${{ env.spellcheckreport }}|sed -e '$a\' >> $GITHUB_OUTPUT +# echo 'EOF' >> $GITHUB_OUTPUT +# +# if [[ "$(cat ${{ env.spellcheckreport }}|wc -l)" != "0" ]] ; then +# echo "proceed=true" >> $GITHUB_OUTPUT +# echo "::notice::Detected some spelling issues with changed markdown" +# cat ${{ env.lintreport }}|sed -e '$a\' +# else +# echo "proceed=false" >> $GITHUB_OUTPUT +# echo "::notice::No spelling issues with changed markdown" +# fi +# - +# name: Other linter errors +# if: ${{ steps.spellcheck.outcome != 'success' && hashFiles(env.spellcheckreport) == '' }} +# run: | +# echo "::error::spellcheck encountered an error" +# exit 1 +# +# pr-markdown-spelling-congrats: +# needs: markdown-spelling +# if: ${{ needs.markdown-spelling.outputs.congrats == 'true' }} +# outputs: +# run_id: ${{ steps.notify_spelling_congrats.outputs.run_id }} +# target_repo: ${{ steps.notify_spelling_congrats.outputs.target_repo }} +# pr_number: ${{ steps.notify_spelling_congrats.outputs.pr_number }} +# pr_sha: ${{ steps.notify_spelling_congrats.outputs.pr_sha }} +# artifact_name: ${{ steps.notify_spelling_congrats.outputs.artifact_name }} +# comment_title: ${{ steps.notify_spelling_congrats.outputs.comment_title }} +# reactions: ${{ steps.notify_spelling_congrats.outputs.reactions }} +# runs-on: ubuntu-latest +# steps: +# - +# name: Congrats +# id: notify_spelling_congrats +# run: | +# mkdir -p artifacts +# read -d '' MSG< "${{ env.artifacts_dir }}/${{ env.spellcheck_artifact }}" +# +# echo "run_id=${{ github.run_id }}" >> "$GITHUB_OUTPUT" +# echo "target_repo=${{ github.repository }}" >> "$GITHUB_OUTPUT" +# echo "pr_number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" +# echo "pr_sha=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT" +# echo "artifact_name=${{ env.spellcheck_artifact }}" >> "$GITHUB_OUTPUT" +# echo "comment_title=${{ env.spellcheck_comment_title }}" >> "$GITHUB_OUTPUT" +# echo "reactions=hooray" >> "$GITHUB_OUTPUT" +# - +# name: Upload comment as artifact +# uses: actions/upload-artifact@v4 +# with: +# path: ${{ env.artifacts_dir }}/${{ env.spellcheck_artifact }} +# name: ${{ env.spellcheck_artifact }} +# +# pr-comment-spelling-congrats: +# name: Create or update comment +# needs: pr-markdown-spelling-congrats +# uses: go-openapi/ci-workflows/.github/workflows/pr-comment.yml@master # <- TODO: caller's ref +# secrets: inherit +# with: +# run_id: ${{ needs.pr-markdown-spelling-congrats.outputs.run_id }} +# target_repo: ${{ needs.pr-markdown-spelling-congrats.outputs.target_repo }} +# pr_number: ${{ needs.pr-markdown-spelling-congrats.outputs.pr_number }} +# pr_sha: ${{ needs.pr-markdown-spelling-congrats.outputs.pr_sha }} +# artifact_name: ${{ needs.pr-markdown-spelling-congrats.outputs.artifact_name }} +# comment_title: ${{ needs.pr-markdown-spelling-congrats.outputs.comment_title }} +# reactions: ${{ needs.pr-markdown-spelling-congrats.outputs.reactions }} +# +# pr-markdown-spelling-report: +# needs: markdown-spelling +# if: ${{ needs.markdown-spelling.outputs.proceed == 'true' }} +# outputs: +# run_id: ${{ steps.notify_spelling_report.outputs.run_id }} +# target_repo: ${{ steps.notify_spelling_report.outputs.target_repo }} +# pr_number: ${{ steps.notify_spelling_report.outputs.pr_number }} +# pr_sha: ${{ steps.notify_spelling_report.outputs.pr_sha }} +# artifact_name: ${{ steps.notify_spelling_report.outputs.artifact_name }} +# comment_title: ${{ steps.notify_spelling_report.outputs.comment_title }} +# reactions: ${{ steps.notify_spelling_report.outputs.reactions }} +# runs-on: ubuntu-latest +# steps: +# - +# name: Pre-process spellcheck report +# id: preprocess +# env: +# MSG: ${{ needs.markdown-spelling.outputs.report }} +# SED_CMD: .github/workflows/scripts/filter.sed # a sed script to parse and reformat the spellcheck report +# JQ_CMD: .github/workflows/scripts/merge.jq # a jq script to dedupe spellcheck reports by file +# TMPFILE: /tmp/spellcheck-report.txt +# run: | +# export MSG +# printenv MSG > "${TMPFILE}" +# # produces a JSON object: +# # { +# # "file.md": [ mispelled word [, ...]] +# # } +# cat "${TMPFILE}" | sed -n -f "${SED_CMD}" | jq -s "${JQ_CMD}" > /tmp/preprocessed.txt +# +# echo 'report<> $GITHUB_OUTPUT +# cat /tmp/preprocessed.txt|sed -e '$a\' >> $GITHUB_OUTPUT +# echo 'EOF' >> $GITHUB_OUTPUT +# - +# name: Format PR comment +# id: comment_formatter +# uses: skills/action-text-variables@v3 +# with: +# template-vars: > +# { +# "text": ${{ steps.preprocess.outputs.report) }} +# } +# template-text: | +# ### ${{ env.spellcheck_comment_title }} +# +# Some mispelled words were detected in the modified .md files. âš ī¸ +# +# Perhaps they were already there before your changes. +# +# This check is advisory only and not blocking. Please help us improve our documentation. +#
+# +# ``` +# ### TODO: range json +# {{ text }} +# ``` +# - +# name: Slap on the wrist +# env: +# OUTPUT: "${{ steps.comment_formatter.outputs.updated-text }}" +# run: | +# mkdir -p "${{ env.artifacts_dir }}" +# printenv OUTPUT > "${{ env.artifacts_dir}}/${{ env.spellcheck_artifact }}" +# +# echo "### Changed markdown docs show some mispelled words. âš ī¸" >> $GITHUB_STEP_SUMMARY +# echo "" >> $GITHUB_STEP_SUMMARY +# echo "> â„šī¸ INFO: we use [rojopolis/spellcheck-github-actions](https://github.com/rojopolis/spellcheck-github-actions)." >> $GITHUB_STEP_SUMMARY +# echo ">" >> $GITHUB_STEP_SUMMARY +# echo "> As a general rule, technical terms or acronyms and references to code objects should be backquoted in markdown" >> $GITHUB_STEP_SUMMARY +# - +# name: Upload comment as artifact +# # description: | +# # Calls a trusted shared workflow that temporarily elevates the caller's privileges +# # to write a comment in the PR. +# uses: actions/upload-artifact@v4 +# with: +# path: ${{ env.artifacts_dir }}/${{ env.spellcheck_artifact }} +# name: ${{ env.spellcheck_artifact }} +# - +# name: Notify +# id: notify_spelling_report +# run: | +# echo "::notice::Commented pull request ${{ github.event.pull_request.number }}" +# +# echo "run_id=${{ github.run_id }}" >> "$GITHUB_OUTPUT" +# echo "target_repo=${{ github.repository }}" >> "$GITHUB_OUTPUT" +# echo "pr_number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" +# echo "pr_sha=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT" +# echo "artifact_name=${{ env.spellcheck_artifact }}" >> "$GITHUB_OUTPUT" +# echo "comment_title=${{ env.spellcheck_comment_title }}" >> "$GITHUB_OUTPUT" +# echo "reactions=confused" >> "$GITHUB_OUTPUT" +# +# pr-comment-spelling-report: +# name: Create or update comment +# needs: pr-markdown-spelling-report +# secrets: inherit +# uses: ./.github/workflows/pr-comment.yml +# with: +# run_id: ${{ needs.pr-markdown-spelling-report.outputs.run_id }} +# target_repo: ${{ needs.pr-markdown-spelling-report.outputs.target_repo }} +# pr_number: ${{ needs.pr-markdown-spelling-report.outputs.pr_number }} +# pr_sha: ${{ needs.pr-markdown-spelling-report.outputs.pr_sha }} +# artifact_name: ${{ needs.pr-markdown-spelling-report.outputs.artifact_name }} +# comment_title: ${{ needs.pr-markdown-spelling-report.outputs.comment_title }} +# reactions: ${{ needs.pr-markdown-spelling-report.outputs.reactions }} diff --git a/experimental/pr-comment.yml b/experimental/pr-comment.yml new file mode 100644 index 0000000..5509847 --- /dev/null +++ b/experimental/pr-comment.yml @@ -0,0 +1,151 @@ +name: pr-comment +# description: | +# This workflow is a trusted workflow that creates or updates comments in PRs. +# +# It may be called by other workflows executing on pull_request_target against the go-openapi repos. +# +# The principle is to communicate the content of a comment between workflows using an uploaded artifact. + +permissions: + pull-requests: write + contents: read + +on: + workflow_call: + inputs: + run_id: + description: | + The run ID of the calling workflow that has emitted the message artifact, e.g. {{ github.event.workflow_run.id }} + type: string + required: true + target_repo: + description: | + The target repository of the PR, e.g. {{ github.repository }} + type: string + required: true + pr_number: + description: | + The pull request number, e.g. {{ github.event.pull_request.number }} + type: string + required: true + pr_sha: + description: | + The commit sha for the originating pull request, e.g. {{github.event.pull_request.head.sha}} + type: string + required: true + artifact_name: + description: | + The reference to the artifact containing the text of the comment. + + At this moment, only supports "markdown_comment.txt" and "spellcheck_comment.txt" + type: string + required: true + comment_title: + description: | + Title is a text string used to uniquely identify a comment that will be upaded on subsequent commits. + type: string + required: true + reactions: + description: | + Optional emoji reaction added to the comment. + type: string + required: false + +env: + GITHUB_API: "https://api.github.com" + MAX_MESSAGE_SIZE: 4096 + +jobs: + pr-comment: + runs-on: ubuntu-latest + env: + TARGET: "${{ inputs.target_repo }}" + steps: + - name: Validate inputs + run: | + rex='^go-openapi\/.*$' + if ! [[ "${{ env.TARGET }}" =~ $rex ]] ; then + echo "This workflow only applies to target repos in github.com/go-openapi." + exit 1 + fi + if [[ "${{ inputs.artifact_name }}" != "markdown_comment.txt" && "${{ inputs.artifact_name }}" != "spellcheck_comment.txt" ]] ; then + echo "This workflow only applies to artifacts named markdown_comment.txt or spellcheck_comment.txt" + exit 1 + fi + + - name: Check originating PR + # description: | + # This check verifies that the originating PR has not been already modified + # by the time this workflow executes. If this is the case, the job is skipped + # and no comment is issued. + id: check_pr + env: + PRN: "${{ inputs.pr_number }}" + run: | + LAST_COMMIT=$(\ + curl -s \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "${GITHUB_API}/repos/${TARGET}/pulls/${PRN}/commits" | \ + jq -r '.[-1].sha'\ + ) + + if [[ "${LAST_COMMIT}" != "${{ inputs.pr_sha }}" ]] ; then + echo "The PR has changed while we were about to commit it. Skip." + echo "proceed=false" >> "${GITHUB_OUTPUT}" + + echo "::warning:: pull request comment skipped because ${{ github.event.pull_request.number }} has changed" + exit 0 + fi + echo "proceed=true" >> "${GITHUB_OUTPUT}" + + - name: Download message artifact + if: ${{ steps.check_pr.outputs.proceed == 'true'}} + id: download + uses: actions/download-artifact@v5 + with: + run-id: "${{ inputs.run_id }}" + repository: "${{ env.TARGET }}" + name: "${{ inputs.artifact_name }}" + path: artifacts/ + github-token: ${{secrets.GITHUB_TOKEN}} + + - name: Check message artifact size + if: ${{ steps.check_pr.outputs.proceed == 'true'}} + run: | + MESSAGE_FILE="${{ steps.download.outputs.download-path }}/${{ inputs.artifact_name }}" + SIZE=$(cat "${MESSAGE_FILE}"|wc -c) + if [[ "${SIZE}" -gt "${{ env.MAX_MESSAGE_SIZE }}" ]] ; then + # truncate the message up to MAX_MESSAGE_SIZE + head -c ${{ env.MAX_MESSAGE_SIZE }} "${MESSAGE_FILE}" > /tmp/truncated + echo >> /tmp/truncated + echo "... (truncated)" >> /tmp/truncated + mv /tmp/truncated "${MESSAGE_FILE}" + echo "::warning:: ${{ inputs.artifact_name }} comment message with size ${SIZE} has been truncated to ${{ env.MAX_MESSAGE_SIZE }} bytes." + fi + + - name: Find previous PR comment + if: ${{ steps.check_pr.outputs.proceed == 'true'}} + uses: peter-evans/find-comment@v3 + id: find_comment + with: + repository: ${{ inputs.target_repo }} + issue-number: ${{ inputs.pr_number }} + body-includes: ${{ inputs.comment_title }} + direction: last + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Create or update PR comment + if: ${{ steps.check_pr.outputs.proceed == 'true'}} + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ inputs.pr_number }} + comment-id: ${{ steps.find_comment.outputs.comment-id }} + reactions: ${{ inputs.reactions }} + reactions-edit-mode: replace + body-path: ${{ steps.download.outputs.download-path }}/${{ inputs.artifact_name }} + edit-mode: replace + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Notify + run: | + echo "::notice::Commented pull request ${{ inputs.pr_number }} with ${{ inputs.artifact_name }}" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2891bf1 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/go-openapi/ci-workflows + +go 1.24.0 + +require github.com/go-openapi/testify/v2 v2.0.2 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1876434 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= diff --git a/sample/pkg/pkg.go b/sample/pkg/pkg.go new file mode 100644 index 0000000..6a54077 --- /dev/null +++ b/sample/pkg/pkg.go @@ -0,0 +1,6 @@ +// Package pkg exercises CI pipelines. +package pkg + +func Pkg() string { + return "" +} diff --git a/sample/pkg/pkg_test.go b/sample/pkg/pkg_test.go new file mode 100644 index 0000000..e381219 --- /dev/null +++ b/sample/pkg/pkg_test.go @@ -0,0 +1,11 @@ +package pkg + +import ( + "testing" + + "github.com/go-openapi/testify/v2/assert" +) + +func TestPkg(t *testing.T) { + assert.Empty(t, Pkg()) +} diff --git a/sample/sample.go b/sample/sample.go new file mode 100644 index 0000000..d38e9a3 --- /dev/null +++ b/sample/sample.go @@ -0,0 +1,6 @@ +// Package sample is used to exercise CI pipelines. +package sample + +func Sample() int { + return 1 +} diff --git a/sample/sample_test.go b/sample/sample_test.go new file mode 100644 index 0000000..e5adc7b --- /dev/null +++ b/sample/sample_test.go @@ -0,0 +1,19 @@ +package sample + +import ( + "testing" + + "github.com/go-openapi/testify/v2/require" +) + +func TestSample(t *testing.T) { + /* + if runtime.GOOS == "windows" { + require.Equal(t, 0, Sample()) // make the test fail on windows + + return + } + + */ + require.Equal(t, 1, Sample()) +}