Suppose you are given two OpenApi Specification (OAS) V3 files :
original-spec.yaml : The original spec
modified-spec.yaml : The modified ( with enhancement / breaking changes / etc )
( an example of both files is available on https://gist.github.com/jy95/d7b53648eb756547eb67fd0db962f68d )
I would like to find a CLI tool that will generates an human readable report
( that I could ,for example, upload it in a PR comment with PR Comment from File
)
My requirements are the following ones :
no technical restriction
( you are free to use any programming language library as long as it
works , including using a OAS to Swagger v2 converter if needed )
human readable / comprehensible
( it should be clear so that people that don't know OAS aren't lost )
maintained
( not mandatory but it is better )
Currently I found these projects on the web :
- openapi-diff
- quen2404/openapi-diff
- Azure/openapi-diff
- ...
( others undoubtedly exist: I leave it to you to find some / or to help me pick one that's good enough... )
Here is the start of a Github workflow to help you kickstart :
name: API Breaking Changes
# Everyone is happy with breaking changes ^^
on:
pull_request:
jobs:
build-report:
needs: build-oas-artefacts
runs-on: ubuntu-latest
steps:
- name: Download OAS file from SOURCE branch
uses: actions/download-artifact#v1
with:
name: original-spec.yaml
- name: Download OAS file from TARGET branch
uses: actions/download-artifact#v1
with:
name: modified-spec.yaml
Thanks for the help
I just wrote this OAS diff tool in golang: https://github.com/Tufin/oasdiff
It outputs json like this:
{
"spec": {
"paths": {
"modified": {
"/": {
"operations": {
"modified": {
"POST": {
"servers": {
"added": [
"https://app.amberflo.io/ingest-endpoint"
],
"deleted": [
"https://app.amberflo.io/ingest"
]
}
}
}
},
"servers": {
"added": [
"https://app.amberflo.io/ingest-endpoint"
],
"deleted": [
"https://app.amberflo.io/ingest"
]
}
}
}
},
"servers": {
"added": [
"https://app.amberflo.io/ingest-endpoint"
],
"deleted": [
"https://app.amberflo.io/ingest"
]
}
},
"summary": {
"diff": true,
"components": {
"paths": {
"modified": 1
},
"servers": {
"added": 1,
"deleted": 1
}
}
}
}
My own answer :
name: API Breaking Changes
# Everyone is happy with breaking changes ^^
on:
pull_request:
jobs:
build-oas-artefacts:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node#v1
with:
node-version: '12.x'
- name: Install required tools
run: |
npm install speccy -g
npm install -g #openapitools/openapi-generator-cli#1.0.10-4.2.3
- name: Set up env variables
run: |
echo "::set-env name=CURRENT_BRANCH::$(echo ${{ github.head_ref }} | sed 's|.*/||')"
echo "::set-env name=TARGET_BRANCH::$(echo ${{ github.base_ref }} | sed 's|.*/||')"
- name: Clone repository in branch ${{ env.TARGET_BRANCH }}
uses: actions/checkout#v2
with:
path: 'A'
ref: ${{ github.base_ref }}
- name: Clone repository in branch ${{ env.CURRENT_BRANCH }}
uses: actions/checkout#v2
with:
path: 'B'
- name: Check if OAS from ${{ env.CURRENT_BRANCH }} is valid
run: |
npx openapi-generator validate -i B/api.yml
- name: Check if OAS from ${{ env.TARGET_BRANCH }} is valid
run: |
npx openapi-generator validate -i A/api.yml
- name: Build OAS specifications
run: |
npx speccy resolve A/api.yml -o original-spec.yaml
npx speccy resolve B/api.yml -o modified-spec.yaml
- name: Upload single OAS file from ${{ env.TARGET_BRANCH }}
uses: actions/upload-artifact#v1
with:
name: original-spec.yaml
path: original-spec.yaml
- name: Upload single OAS file from ${{ env.CURRENT_BRANCH }}
uses: actions/upload-artifact#v1
with:
name: modified-spec.yaml
path: modified-spec.yaml
build-report:
needs: build-oas-artefacts
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node#v1
with:
node-version: '13.x'
- name: Install required tools
run: |
npm install openapi-diff -g
- name: Download OAS file from SOURCE branch
uses: actions/download-artifact#v1
with:
name: original-spec.yaml
path: specs/
- name: Download OAS file from TARGET branch
uses: actions/download-artifact#v1
with:
name: modified-spec.yaml
path: specs/
- name: Set up env variables
run: |
echo "::set-env name=ORIGIN_SPEC::$(echo "$(pwd)/specs/original-spec.yaml" )"
echo "::set-env name=MODIFIED_SPEC::$(echo "$(pwd)/specs/modified-spec.yaml" )"
echo "::set-env name=SPECS_LOG::$(echo "$(pwd)/breaking-changes.log" )"
echo "::set-env name=JSON_DIFF_FILE::$(echo "$(pwd)/breaking-changes.json" )"
echo "::set-env name=GH_COMMENT_FILE::$(echo "$(pwd)/Github-comment.md" )"
# See : https://github.com/actions/download-artifact/issues/14
- name: Restore permissions
run: |
chmod -R 777 ${{ env.ORIGIN_SPEC }}
chmod -R 777 ${{ env.MODIFIED_SPEC }}
- name: Generate report
run: |
openapi-diff ${{ env.ORIGIN_SPEC }} ${{ env.MODIFIED_SPEC }} | tee ${{ env.SPECS_LOG }}
sed -n '1!p' ${{ env.SPECS_LOG }} > ${{ env.JSON_DIFF_FILE }}
- uses: actions/upload-artifact#v1
with:
name: openapi-diff.json
path: breaking-changes.json
- name: Prepare comment on Github PR
run: |
sed -n '1p' ${{ env.SPECS_LOG }} >> ${{ env.GH_COMMENT_FILE }}
printf "\n\n\`\`\`yaml\n" >> ${{ env.GH_COMMENT_FILE }}
sed -n '1!p' ${{ env.SPECS_LOG }} >> ${{ env.GH_COMMENT_FILE }}
printf "\n\`\`\`\n" >> ${{ env.GH_COMMENT_FILE }}
- name: comment PR
uses: machine-learning-apps/pr-comment#master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
path: Github-comment.md
Related
I have my GitHub Repository linked to my vercel project. Required for deploying changes on a subdomain that's linked to a branch on my git repository. dev.<website>.<tld> is linked to the dev branch. <website>.<tld> is linked to the main branch. Couldn't figure out another way to do this, other than linking to my git repo.
I have a GitHub action that runs tests amongst other things that I want to ensure pass before deployment. But every time I push changes to my production branch it kicks of a vercel deployment, that I want to avoid.
I know there is a Ignored Build Step section in the git settings on vercel, but I'm not sure what to add in the command input in this section?
I've added
[ exit 1 ]
But not sure if this is correct.
Here is my github actions workflow yml file.
name: Deploy Web Application
on:
push:
branches: ["master", "development"]
paths:
- "web/**"
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./web
strategy:
matrix:
node-version: [16.13.0]
steps:
- name: Checkout
uses: actions/checkout#v3
- name: Node Version ${{ matrix.node-version }}
uses: actions/setup-node#v3
with:
node-version: ${{ matrix.node-version }}
cache: "yarn"
cache-dependency-path: "./web/yarn.lock"
- name: Install Dependencies
run: yarn
- name: Build Project
run: yarn build
- name: Run Linting
run: yarn lint
- name: Run Typecheck
run: yarn typecheck
- name: Start Deployment
uses: bobheadxi/deployments#v0.6.2
id: deployment
with:
step: start
token: ${{ secrets.GH_TOKEN }}
env: prod
- name: Deploy to Vercel
uses: amondnet/vercel-action#v20
id: vercel-action
with:
github-token: ${{ secrets.GH_TOKEN }}
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
# if main branch go to --prod environment otherwise nothing for preview environment
vercel-args: "${{ github.ref == 'refs/heads/master' && '--prod' || '' }}"
working-directory: ./web
- name: Update Deployment Status
uses: bobheadxi/deployments#v0.6.2
if: always()
with:
step: finish
token: ${{ secrets.GH_TOKEN }}
status: ${{ job.status }}
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
env_url: ${{ steps.vercel-action.outputs.preview-url }}
You can enable the "Ignored Build Step" field by referring to the
documentation of this feature. If the command returns "0", the
build will be skipped. If, however, a code "1" or greater is
returned, then a new deployment will be built.
With a Script
#!/bin/bash
echo "VERCEL_ENV: $VERCEL_ENV"
if [[ "$VERCEL_ENV" == "production" ]] ; then
# Proceed with the build
echo "β
- Build can proceed"
exit 1;
else
# Don't build
echo "π - Build cancelled"
exit 0;
fi
With Environment Variables
#!/bin/bash
echo "VERCEL_GIT_COMMIT_REF: $VERCEL_GIT_COMMIT_REF"
if [[ "$VERCEL_GIT_COMMIT_REF" == "staging" || "$VERCEL_GIT_COMMIT_REF" == "main" ]] ; then
# Proceed with the build
echo "β
- Build can proceed"
exit 1;
else
# Don't build
echo "π - Build cancelled"
exit 0;
fi
With Folders and Workspaces
To build a new deployment considering only a certain folder, you can use the following command:
git diff HEAD^ HEAD --quiet ./packages/frontend/
The above was copied from here (includes more details):
How do I use the "Ignored Build Step" field on Vercel?
Video tutorial:
How to Ignore Build Step on Vercel
Another guide:
Vercel β ignore GitHub branches
I'm currently deploying a security tool for my cluster. It worked well but I want to reduce the length of the code and avoid repeating code inside the file.
Here is the situation:
on:
pull_request:
path:
- 'ionos/terraform/dev/*.tf'
- 'ionos/terraform/prod/*/*/*.tf'
jobs:
# JOB to run change detection
changes:
runs-on: ubuntu-latest
# Set job outputs to values from filter step
outputs:
Ionos_dev: ${{ steps.filter.outputs.Ionos_dev }}
Ionos_prod: ${{ steps.filter.outputs.Ionos_prod }}
steps:
# For pull requests it's not necessary to checkout the code
- uses: dorny/paths-filter#v2
id: filter
with:
filters: |
Ionos_dev:
- 'ionos/terraform/dev/*.tf'
Ionos_prod:
- 'ionos/terraform/prod/*/*/*.tf'
Duplicated part
Ionos_prod:
name: tfsec sarif report ionos_prod
needs: changes
if: ${{ needs.changes.outputs.Ionos_prod == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Clone repo
uses: actions/checkout#master
- name: tfsec sarif ionos_dev
uses: aquasecurity/tfsec-sarif-action#v0.1.0
with:
working_directory: ionos/terraform/prod/
sarif_file: tfsec.sarif
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif#v1
with:
sarif_file: tfsec.sarif
Ionos_dev:
name: tfsec sarif report ionos_dev
needs: changes
if: ${{ needs.changes.outputs.Ionos_dev == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Clone repo
uses: actions/checkout#master
- name: tfsec sarif ionos_dev
uses: aquasecurity/tfsec-sarif-action#v0.1.0
with:
working_directory: ionos/terraform/dev/
sarif_file: tfsec.sarif
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif#v1
with:
sarif_file: tfsec.sarif
I have more than 2 duplicated jobs, that's why I want to make the job as a variable.
My problem is I don't see how can I create the job as a variable and pass these two variables inside the job just created:
if: ${{ needs.changes.outputs.Ionos_prod == 'true' }}
&
working_directory: ionos/terraform/prod/
Anny suggestions?
After few days of research, and based on that documentation (I haven't found it before):
https://docs.github.com/pt/actions/using-jobs/using-a-matrix-for-your-jobs
I finally solved my problem.
Here is the code and some explanations at the end of it.
on:
pull_request:
types: [synchronize, reopened, labeled]
paths:
- 'aws/dns/domains/**'
- 'ionos/terraform/prod/**'
- 'ionos/terraform/dev/**'
- 'azure/terraform/**'
jobs:
changes:
runs-on: ubuntu-latest
#Outputs gives a bool variable. If a file in the path has been change -- true
outputs:
Ionos_dev: ${{ steps.filter.outputs.Ionos_dev }}
Ionos_prod: ${{ steps.filter.outputs.Ionos_prod }}
aws: ${{ steps.filter.outputs.aws }}
azure: ${{ steps.filter.outputs.azure }}
steps:
#Use of an action which check if a file in a path has been change.
- uses: dorny/paths-filter#v2
id: filter
with:
filters: |
Ionos_dev:
- 'ionos/terraform/dev/**/*.tf'
Ionos_prod:
- 'ionos/terraform/prod/**/*.tf'
aws:
- 'aws/dns/domains/**/*.tf'
azure:
- 'azure/terraform/prod/**/*.tf'
tfsec_scan_matrix:
name: tfsec_sarif_report_all_directory
runs-on: ubuntu-latest
#Here we point the job changes, required for this job
needs: changes
#We create a matrix to store the output of each repo (true or false)
#Each filter link with its directory (the directory is use to indicate the scan which directory it has to scan)
strategy:
matrix:
include:
- filters: ${{ needs.changes.outputs.Ionos_dev }}
working_directory: ionos/terraform/dev/
- filters: ${{ needs.changes.outputs.Ionos_prod }}
working_directory: ionos/terraform/prod/
- filters: ${{ needs.changes.outputs.aws }}
working_directory: aws/dns/domains/
- filters: ${{ needs.changes.outputs.azure }}
working_directory: azure/terraform/prod/
steps:
#if the path has been modified, then clone repo, same thing for the others steps
- if: ${{ matrix.filters == 'true' }}
name: Clone repo
uses: actions/checkout#master
- if: ${{ matrix.filters == 'true' }}
name: tfsec sarif ionos_dev
uses: aquasecurity/tfsec-sarif-action#v0.1.0
with:
working_directory: ${{ matrix.working_directory }}
sarif_file: tfsec.sarif
- if: ${{ matrix.filters == 'true' }}
name: Upload SARIF file
uses: github/codeql-action/upload-sarif#v2
with:
sarif_file: tfsec.sarif
What does it do?
If a tf file has been change on a pull-request in a particular path then it runs on this specific path a tfsec scan.
To solve my problem:
I implemented a matrix inside a job:
strategy:
matrix:
include:
- filters: ${{ needs.changes.outputs.Ionos_dev }}
working_directory: ionos/terraform/dev/
- filters: ${{ needs.changes.outputs.Ionos_prod }}
working_directory: ionos/terraform/prod/
- filters: ${{ needs.changes.outputs.aws }}
working_directory: aws/dns/domains/
- filters: ${{ needs.changes.outputs.azure }}
working_directory: azure/terraform/prod/
EXTRA: The "include" parameter, in my case, is to assigned an outputs to its specific path.
However, if I wanted to combine all the possibilities, I would have done this way:
strategy:
matrix:
filter: [Ionos_dev, Ionos_prod, aws, azure]
working_directory: [Ionos_dev, ionos/terraform/prod/, aws/dns/domains/, azure/terraform/prod/]
In this case, it will run all the 9 possibilities.
steps:
#if the path has been modified, then clone repo, same thing for the others steps
- if: ${{ matrix.filters == 'true' }}
name: Clone repo
uses: actions/checkout#master
- if: ${{ matrix.filters == 'true' }}
name: tfsec sarif ionos_dev
uses: aquasecurity/tfsec-sarif-action#v0.1.0
with:
working_directory: ${{ matrix.working_directory }}
sarif_file: tfsec.sarif
- if: ${{ matrix.filters == 'true' }}
name: Upload SARIF file
uses: github/codeql-action/upload-sarif#v2
with:
sarif_file: tfsec.sarif
For this part, I am still working on it. I try to improve it by simplify to only one 'if'
Following is how our.drone.yml looks like (and template also listed below) this an example configuration very much how we want in our production. The reason we are using a template is that our staging and production have similar configurations with values different in them(hence circuit template). And we wanted to remove duplication using the template circuit.yaml.
But currently, we are unable to do so df I donβt define the test.yaml(template) and have test step imported without template (and have the circuit template define to avoid the duplicate declaration of staging and production build) the drone build fails with
"template converter: template name given not found
If I define the test step as a template. I see the test step working but on creating the tag I see the following error
{"commit":"28ac7ad3a01728bd1e9ec2992fee36fae4b7c117","event":"tag","level":"info","msg":"trigger: skipping build, no matching pipelines","pipeline":"test","ref":"refs/tags/v1.4.0","repo":"meetme2meat/drone-example","time":"2022-01-07T19:16:15+05:30"}
---
kind: template
load: test.yaml
data:
commands:
- echo "machine github.com login $${GITHUB_LOGIN} password $${GITHUB_PASSWORD}" > /root/.netrc
- chmod 600 /root/.netrc
- go clean -testcache
- echo "Running test"
- go test -race ./...
---
kind: template
load: circuit.yaml
data:
deploy: deploy
create_tags:
commands:
- echo "Deploying version $DRONE_SEMVER"
- echo -n "$DRONE_SEMVER,latest" > .tags
backend_image:
version: ${DRONE_SEMVER}
tags:
- '${DRONE_SEMVER}'
- latest
And the template is below
test.yaml
kind: pipeline
type: docker
name: test
steps:
- name: test
image: golang:latest
environment:
GITHUB_LOGIN:
from_secret: github_username
GITHUB_PASSWORD:
from_secret: github_token
commands:
{{range .input.commands }}
- {{ . }}
{{end}}
volumes:
- name: deps
path: /go
- name: build
image: golang:alpine
commands:
- go build -v -o out .
volumes:
- name: deps
path: /go
volumes:
- name: deps
temp: {}
trigger:
branch:
- main
event:
- push
- pull_request
circuit.yaml
kind: pipeline
type: docker
name: {{ .input.deploy }}
steps:
- name: create-tags
image: alpine
commands:
{{range .input.create_tags.commands }}
- {{ . }}
{{end}}
- name: build
image: plugins/docker
environment:
GITHUB_LOGIN:
from_secret: github_username
GITHUB_PASSWORD:
from_secret: github_token
VERSION: {{ .input.backend_image.version }}
SERVICE: circuits
settings:
auto_tag: false
repo: ghcr.io/meetme2meat/drone-ci-example
registry: ghcr.io
I tried to create a github action that operates on ./example folder. Code that is inside /.example folder was built by using create-react-app.
Workflow code:
name: CI
on:
push:
branches: [ origin ]
pull_request:
branches: [ origin ]
jobs:
build_test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 15.x]
steps:
- uses: actions/checkout#v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node#v2
with:
node-version: ${{ matrix.node-version }}
- name: npm install, build, and test example
working-directory: example
run: |
npm install
npm run build --if-present
But it causes an error:
The structure of my project (inside ./example there is package.json):
Your workflow yaml file should look like below.
name: CI
on:
push:
branches: [ origin ]
pull_request:
branches: [ origin ]
jobs:
build_test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 15.x]
steps:
- uses: actions/checkout#v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node#v2
with:
node-version: ${{ matrix.node-version }}
- name: npm install, build, and test example
working-directory: ./example
run: |
npm install
npm run build --if-present
I am trying to implement Github-actions(bot), which runs gradle test when PR has been created.
To assure that my workflow file works as I expected, I explicitly wrote a test method which should cause failure.
#Test
fun thisShouldFail() {
assertEquals(1, 2)
}
When I try testing on my local machine, I get the log below.
> Task :test FAILED
FAILURE: Build failed with an exception.
# More
Above log indicates that there was something wrong in the test codes as I expected it to be.
But then Github actions bot runs this command, the test code result is SUCCESS.
Below is my github workflow yaml file for this action.
name: PullRequestGradleTest
on:
pull_request_target:
types: [labeled]
jobs:
test:
name: GradleTest
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'STAGING')
steps:
- name: checkout
uses: actions/checkout#v2
- name: Setup JDK 1.8
uses: actions/setup-java#v2
with:
java-version: '8'
distribution: 'adopt'
- name: Grant Permissions to gradlew
run: chmod +x gradlew
- name: Test
run: gradle test --tests "*"
- name: Test Success
if: success()
uses: actions/github-script#0.2.0
with:
github-token: ${{ github.token }}
script: |
const pull_number = "${{github.event.number}}"
await github.pulls.createReview({
...context.repo,
pull_number,
body: "All tests passed.",
event: "APPROVE"
})
- name: Test Fail
if: failure()
uses: actions/github-script#0.2.0
with:
github-token: ${{ github.token }}
script: |
const pull_number = "${{github.event.number}}"
await github.pulls.createReview({
...context.repo,
pull_number,
body: "There is something wrong with test codes.",
event: "REQUEST_CHANGES"
})
await github.pulls.update({
...context.repo,
pull_number,
state: "closed"
})
I found that you are using gradle, not gradlew.
name: PullRequestGradleTest
on:
pull_request_target:
types: [labeled]
jobs:
test:
name: GradleTest
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'STAGING')
steps:
- name: checkout
uses: actions/checkout#v2
- name: Setup JDK 1.8
uses: actions/setup-java#v2
with:
java-version: '8'
distribution: 'adopt'
- name: Grant Permissions to gradlew
run: chmod +x gradlew
- name: Test
run: ./gradlew test --tests "*"
- name: Test Success
if: success()
uses: actions/github-script#0.2.0
with:
github-token: ${{ github.token }}
script: |
const pull_number = "${{github.event.number}}"
await github.pulls.createReview({
...context.repo,
pull_number,
body: "All tests passed.",
event: "APPROVE"
})
- name: Test Fail
if: failure()
uses: actions/github-script#0.2.0
with:
github-token: ${{ github.token }}
script: |
const pull_number = "${{github.event.number}}"
await github.pulls.createReview({
...context.repo,
pull_number,
body: "There is something wrong with test codes.",
event: "REQUEST_CHANGES"
})
await github.pulls.update({
...context.repo,
pull_number,
state: "closed"
})
If you use gradle in the command, it will depend on the machine's environment. In this case, there is a possibility that occurs error because of Gradle version. Therefore, you need to use the project's Gradle which is included with your repo. The way to use is using gradlew scripts.
I also recommend following these three steps to test the branch for the pull request.
Clean -> Assemble(or build) -> Test
Base problem was that if we use pull_request_target event, the action runs on the target branch, which would be the base branch that the PR will be merged into. To solve this problem, I had to explicitly set where this action will run on.
On job => steps
steps:
- name: checkout
uses: actions/checkout#v2
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{github.event.pull_request.head.repo.full_name }}