From cdb224057e949e940ad5118a88aeaa8f0e76daac Mon Sep 17 00:00:00 2001 From: Joaquin Trillo Date: Mon, 16 Dec 2024 11:45:47 +0100 Subject: [PATCH] Bootcamp update 2024 --- .github/workflows/cd-docker.yml | 6 +- .github/workflows/cd-using-docker-actions.yml | 72 ++++++++++++++ .github/workflows/reusable-node-build.yml | 2 +- .github/workflows/test-custom-action.yml | 3 +- .start-code/hangman-api/Dockerfile | 3 +- 01-setup-ci/readme.md | 5 - 04-working-with-build-artifacts/readme.md | 26 +++-- 06-service-containers/readme.md | 13 ++- 08-continous-delivery/readme.md | 30 +++--- .../01-creating-reusable-workflow/readme.md | 4 +- 09-workflow-calls/readme.md | 6 +- 10-custom-action/action-files/action.yml | 6 +- 10-custom-action/action-files/index.js | 40 +++++--- 10-custom-action/readme.md | 99 +++++++++++++++---- 14 files changed, 242 insertions(+), 73 deletions(-) create mode 100644 .github/workflows/cd-using-docker-actions.yml diff --git a/.github/workflows/cd-docker.yml b/.github/workflows/cd-docker.yml index 5aeb494..a678220 100644 --- a/.github/workflows/cd-docker.yml +++ b/.github/workflows/cd-docker.yml @@ -23,7 +23,7 @@ jobs: # run: | # npm ci # npm run build --if-present - # - uses: actions/upload-artifact@v3 + # - uses: actions/upload-artifact@v4 # with: # name: build-code # path: hangman-api/dist/ @@ -34,14 +34,14 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: build-code path: hangman-api/dist/ - name: Build and Push Docker Image working-directory: ./hangman-api env: - DOCKER_USER: "jaimesalas" + DOCKER_USER: "jaimesalas" # DOCKER_REPOSITORY: "hangman-api" DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} run: | diff --git a/.github/workflows/cd-using-docker-actions.yml b/.github/workflows/cd-using-docker-actions.yml new file mode 100644 index 0000000..67474ad --- /dev/null +++ b/.github/workflows/cd-using-docker-actions.yml @@ -0,0 +1,72 @@ +name: Docker Image hangman API CD (using Docker actions) + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: hangman-api/package-lock.json + - name: build + working-directory: ./hangman-api + run: | + npm ci + npm run build --if-present + - uses: actions/upload-artifact@v4 + with: + name: build-code + path: hangman-api/dist/ + + # delivery: + # runs-on: ubuntu-latest + # needs: build + + # steps: + # - uses: actions/checkout@v4 + # - uses: actions/download-artifact@v4 + # with: + # name: build-code + # path: hangman-api/dist/ + # - name: Build and Push Docker Image + # working-directory: ./hangman-api + # env: + # DOCKER_USER: "jaimesalas" # + # DOCKER_REPOSITORY: "hangman-api" + # DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + # run: | + # echo $DOCKER_PASSWORD | docker login --username $DOCKER_USER --password-stdin + # image=$DOCKER_USER/$DOCKER_REPOSITORY:$(date +%s) + # docker build -t $image -f Dockerfile.workflow . + # docker push $image + + delivery: + runs-on: ubuntu-latest + needs: build + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Download dist folder as artifact + uses: actions/download-artifact@v4 + - name: Docker Hub login + uses: docker/login-action@v3 + with: + # registry: by default is set to Docker Hub + username: jtrillo # + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build and push Docker Image + uses: docker/build-push-action@v5 + with: + context: ./hangman-api + push: true + tags: jtrillo/hangman-api-actions:latest # + file: ./hangman-api/Dockerfile.workflow diff --git a/.github/workflows/reusable-node-build.yml b/.github/workflows/reusable-node-build.yml index dab8f32..93a42fa 100644 --- a/.github/workflows/reusable-node-build.yml +++ b/.github/workflows/reusable-node-build.yml @@ -26,7 +26,7 @@ jobs: run: | npm ci npm run build --if-present - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: build-code path: ${{ inputs.working-directory }}/dist/ diff --git a/.github/workflows/test-custom-action.yml b/.github/workflows/test-custom-action.yml index 5bc56df..84b7512 100644 --- a/.github/workflows/test-custom-action.yml +++ b/.github/workflows/test-custom-action.yml @@ -13,6 +13,7 @@ jobs: uses: jtrillo/get-commodity-price@v1.0.1 with: commodity: silver + currency: EUR # Use the output from 'commodity_price' step - name: Get the output price - run: echo "Price per ounce is ${{ steps.commodity_price.outputs.price }} USD" + run: echo "Price per ounce is ${{ steps.commodity_price.outputs.price }} ${{ steps.commodity_price.inputs.currency }}" diff --git a/.start-code/hangman-api/Dockerfile b/.start-code/hangman-api/Dockerfile index a2e24e3..4457cec 100644 --- a/.start-code/hangman-api/Dockerfile +++ b/.start-code/hangman-api/Dockerfile @@ -23,5 +23,4 @@ ENV NODE_ENV=production RUN npm install - -CMD ["npm", "start"] +CMD ["npm", "start"] \ No newline at end of file diff --git a/01-setup-ci/readme.md b/01-setup-ci/readme.md index 5298f41..84de6c2 100644 --- a/01-setup-ci/readme.md +++ b/01-setup-ci/readme.md @@ -31,8 +31,6 @@ jobs: whoami pwd node -v - - ``` Let's commit this file to a new branch: @@ -149,7 +147,6 @@ jobs: - whoami - pwd - node -v - ``` ```yaml @@ -210,9 +207,7 @@ jobs: run: | npm ci npm run build --if-present - ls ./dist npm test - ``` * With `paths` we filter the directories that are able to trigger a new workflow. diff --git a/04-working-with-build-artifacts/readme.md b/04-working-with-build-artifacts/readme.md index 3204f68..0020631 100644 --- a/04-working-with-build-artifacts/readme.md +++ b/04-working-with-build-artifacts/readme.md @@ -47,10 +47,11 @@ jobs: npm ci npm run build --if-present # diff # - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: dependencies path: hangman-api/node_modules/ + include-hidden-files: true # diff # ``` @@ -72,10 +73,11 @@ jobs: run: | npm ci npm run build --if-present - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: dependencies path: hangman-api/node_modules/ + include-hidden-files: true test: runs-on: ubuntu-latest @@ -84,14 +86,17 @@ jobs: steps: - uses: actions/checkout@v4 # diff - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: dependencies path: hangman-api/node_modules - # diff + include-hidden-files: true - name: test working-directory: ./hangman-api - run: npm test # npm ci is not needed here + run: | + chmod -R +x node_modules + npm test + # diff ``` Let's push the changes done. @@ -104,7 +109,9 @@ git push And trigger the workflow from project site. -If we check the workflow, we will notice that is taking a long time to resolve the dependencies upload. This is not the way to manage this, let's try something different. +**DEPRECATED.** ~~If we check the workflow, we will notice that is taking a long time to resolve the dependencies upload~~. **Both actions in version 4 improve significatly the performance. Check this article: **. + +However, this approach is not the proper way to manage this, let's try something different: using the cache. * [Caching dependencies to speed up workflows](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows) @@ -127,10 +134,11 @@ jobs: run: | npm ci npm run build --if-present -- - uses: actions/upload-artifact@v3 +- - uses: actions/upload-artifact@v4 - with: - name: dependencies - path: hangman-api/node_modules/ +- include-hidden-files: true test: runs-on: ubuntu-latest @@ -138,16 +146,18 @@ jobs: steps: - uses: actions/checkout@v4 -- - uses: actions/download-artifact@v3.0.0 +- - uses: actions/download-artifact@v4 - with: - name: dependencies - path: hangman-api/node_modules +- include-hidden-files: true + - uses: actions/setup-node@v4 + with: + node-version: 16 - name: test working-directory: ./hangman-api run: | +- chmod -R +x node_modules + npm ci npm test ``` diff --git a/06-service-containers/readme.md b/06-service-containers/readme.md index 28a9d34..b5c99db 100644 --- a/06-service-containers/readme.md +++ b/06-service-containers/readme.md @@ -8,7 +8,7 @@ The previous solution for integration test works, but have some downsides. * Dockerfile.migrations * Dockerfile.test-integration -This code is not related with our solution, is just there to solve the CI issue. Well is not the end of the world, but is thera another way that we can solve this? Let's introduce [Service Containers](https://docs.github.com/en/actions/using-containerized-services/about-service-containers) +This code is not related with our solution, it is just there to solve the CI issue. Well, this is not the end of the world, but is there another way to execute the integration tests without adding these files? Let's introduce [Service Containers](https://docs.github.com/en/actions/using-containerized-services/about-service-containers) > **About service containers** - Service containers are Docker containers that provide a simple and portable way for you to host services that you might need to test or operate your application in a workflow. For example, your workflow might need to run integration tests that require access to a database and memory cache. @@ -71,7 +71,7 @@ git commit -m "added database relationships step" git push ``` -* Run the workflow manually from GiHub website. +* Run the workflow manually from GitHub website. If everything goes right we must see and output as follows: @@ -138,3 +138,12 @@ Snapshots: 0 total Time: 3.519 s Ran all test suites. ``` + +* Before moving on, let's delete files added in the previous demo (`wait-for-it.sh`, `test-integration.yml`, `Dockerfile.migrations` and `Dockerfile.test-integration`) to verify that service containers approach does not need them. + +```bash +rm wait-for-it.sh test-integration.yml Dockerfile.migrations Dockerfile.test-integration +git add . +git commit -m "Delete no longer required files to run the integration tests" +git push +``` diff --git a/08-continous-delivery/readme.md b/08-continous-delivery/readme.md index e00dc69..3bd7180 100644 --- a/08-continous-delivery/readme.md +++ b/08-continous-delivery/readme.md @@ -14,13 +14,13 @@ Before adding the required steps into our workflow, let's try it on local. First from `hangman-api` root directory run: ```bash -docker build -t jaimesalas/hangman-api . +docker build -t /hangman-api . ``` And test by running: ```bash -docker run -d -p 3000:3000 jaimesalas/hangman-api +docker run -d -p 3000:3000 /hangman-api ``` ```bash @@ -31,7 +31,7 @@ We could also push it to Docker Hub ```bash docker login # Just if not already logged -docker push jaimesalas/hangman-api +docker push /hangman-api ``` ## Building the Docker Image in a workflow @@ -115,7 +115,7 @@ CMD ["npm", "start"] We should use the following command to build an image with an specific Node.js version: ```bash -docker build -t jtrillo/hangman-api -f Dockerfile.workflow --build-arg version=20.10-alpine . +docker build -t /hangman-api -f Dockerfile.workflow --build-arg version=20.10-alpine . ``` First, we're going to use the same build job as the one on `ci.yml`. But here we're going to upload the build as an artifact to be used on a new `delivery` job: @@ -144,7 +144,7 @@ jobs: run: | npm ci npm run build --if-present - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: build-code path: hangman-api/dist/ @@ -161,7 +161,7 @@ Now we need to build the image before push it to Docker registry we can do this steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: build-code path: hangman-api/dist/ @@ -195,7 +195,7 @@ Ok, almost done. There are prebaked actions to authenticate and push Docker imag + - name: Build and Push Docker Image working-directory: ./hangman-api + env: -+ DOCKER_USER: "jaimesalas" ++ DOCKER_USER: "jaimesalas" # + DOCKER_REPOSITORY: "hangman-api" + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker build . --file Dockerfile.workflow --tag my-image-name:$(date +%s) @@ -224,8 +224,8 @@ And fire the workflow from GitHub site. If everything works we must see somethin ```console ---> 12c19641de84 Successfully built 12c19641de84 -Successfully tagged jaimesalas/hangman-api:1665501807 -The push refers to repository [docker.io/jaimesalas/hangman-api] +Successfully tagged /hangman-api:1665501807 +The push refers to repository [docker.io//hangman-api] ``` And visit Docker Hub to find out the uploaded image @@ -238,10 +238,10 @@ In this case, we will use actions instead of commands. We are going to make use * [docker/setup-buildx-action](https://github.com/marketplace/actions/docker-setup-buildx) * [docker/build-push-action](https://github.com/marketplace/actions/build-and-push-docker-images) -Let's add a new job to our workflow. +Let's update `delivery` job: ```yaml - buildAndPush: + delivery: runs-on: ubuntu-latest needs: build @@ -249,12 +249,12 @@ Let's add a new job to our workflow. - name: Checkout repo uses: actions/checkout@v4 - name: Download dist folder as artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 - name: Docker Hub login uses: docker/login-action@v3 with: # registry: by default is set to Docker Hub - username: jtrillo + username: jtrillo # password: ${{ secrets.DOCKER_PASSWORD }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -263,7 +263,7 @@ Let's add a new job to our workflow. with: context: ./hangman-api push: true - tags: jtrillo/hangman-api-actions:latest + tags: jtrillo/hangman-api-actions:latest # file: ./hangman-api/Dockerfile.workflow ``` @@ -271,7 +271,7 @@ Push the new changes ```bash git add . -git commit -m "added buildAndPush job" +git commit -m "update delivery job" git push ``` diff --git a/09-workflow-calls/01-creating-reusable-workflow/readme.md b/09-workflow-calls/01-creating-reusable-workflow/readme.md index 5e92932..0389cb1 100644 --- a/09-workflow-calls/01-creating-reusable-workflow/readme.md +++ b/09-workflow-calls/01-creating-reusable-workflow/readme.md @@ -40,7 +40,7 @@ jobs: run: | npm ci npm run build --if-present - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: build-code path: ${{ inputs.working-directory }}/dist/ @@ -75,7 +75,7 @@ jobs: - run: | - npm ci - npm run build --if-present -- - uses: actions/upload-artifact@v3 +- - uses: actions/upload-artifact@v4 - with: - name: build-code - path: hangman-api/dist/ diff --git a/09-workflow-calls/readme.md b/09-workflow-calls/readme.md index 61f8c33..331ae2e 100644 --- a/09-workflow-calls/readme.md +++ b/09-workflow-calls/readme.md @@ -31,10 +31,10 @@ After you add a `workflow_call` trigger, you need to make sure that your reposit There are [limitations](https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations). Some of them are: * **You can't reference a reusable workflow that's in a private repository**. If you have a reusable workflow in a private repository, only other workflows in that private repository can use it. +* **Reusable workflows cant be stacked on top of one another, but there is a limit**. You can connect a maximum of four levels of workflows - that is, the top-level caller workflow and up to three levels of reusable workflows. For example: caller-workflow.yml → called-workflow-1.yml → called-workflow-2.yml → called-workflow-3.yml. Loops in the workflow tree are not permitted. More info [here](https://docs.github.com/en/actions/sharing-automations/reusing-workflows#nesting-reusable-workflows) +* **You can call a maximum of 20 unique reusable workflows from a single worklow file**. This limit includes nested/stacked reusable workflows. -* **Reusable workflows can’t be stacked on top of one another**. You can only have a reusable workflow call another reusable workflow, but you can’t have it reference more than one. - -## Reusable workflows vs.composite actions +## Reusable workflows vs composite actions When we launched reusable workflows, one of the first questions we got was around how they’re different from composite actions. diff --git a/10-custom-action/action-files/action.yml b/10-custom-action/action-files/action.yml index add2848..fb1f93f 100644 --- a/10-custom-action/action-files/action.yml +++ b/10-custom-action/action-files/action.yml @@ -5,9 +5,13 @@ inputs: description: 'Commodity (gold or silver)' required: true default: 'gold' + currency: # id of input + description: 'Currency (USD or EUR)' + required: true + default: 'USD' outputs: price: # id of output description: 'The current price of the select commodity' runs: - using: 'node16' + using: 'node20' main: 'index.js' \ No newline at end of file diff --git a/10-custom-action/action-files/index.js b/10-custom-action/action-files/index.js index 155aec7..b4d9438 100644 --- a/10-custom-action/action-files/index.js +++ b/10-custom-action/action-files/index.js @@ -1,25 +1,41 @@ -const core = require('@actions/core'); -const github = require('@actions/github'); +import { getInput, setOutput, setFailed } from '@actions/core'; +import { context } from '@actions/github'; try { // `commodity` input defined in action metadata file - const commodity = core.getInput('commodity'); - console.log(`Getting current ${commodity} price per ounce...`); + const commodity = getInput('commodity'); + + // Check if input is valid + if (commodity.toLowerCase() !== 'gold' && commodity.toLowerCase() !== 'silver') { + throw new Error(`Commodity ${commodity} is not valid`); + } + + // `currency` input defined in action metadata file + const currency = getInput('currency'); + + // Check if input is valid + if (currency.toUpperCase() !== 'USD' && currency.toUpperCase() !== 'EUR') { + throw new Error(`Currency ${currency} is not valid`); + } + + console.log(`Getting current ${commodity} price per ounce in ${currency}...`); let price = 0; + if (commodity.toLowerCase() === 'gold') { - price = 2019.80; // https://www.bullionvault.com/gold-price-chart.do - console.log(`Current gold price per ounce: ${price} USD`); - } else if (commodity.toLowerCase() === 'silver') { - price = 24.07; // https://www.bullionvault.com/silver-price-chart.do - console.log(`Current silver price per ounce: ${price} USD`); + // https://www.bullionvault.com/gold-price-chart.do + price = currency.toUpperCase() === 'USD' ? 2652.84 : 2524.52; + } else { + // https://www.bullionvault.com/silver-price-chart.do + price = currency.toUpperCase() === 'USD' ? 30.58 : 29.10; } + console.log(`Current ${commodity} price per ounce: ${price} ${currency}`); // `price` output defined in action metadata file - core.setOutput('price', price); + setOutput('price', price); // Get the JSON webhook payload for the event that triggered the workflow - const payload = JSON.stringify(github.context.payload, undefined, 2) + const payload = JSON.stringify(context.payload, undefined, 2) console.log(`The event payload: ${payload}`); } catch (error) { - core.setFailed(error.message); + setFailed(error.message); } \ No newline at end of file diff --git a/10-custom-action/readme.md b/10-custom-action/readme.md index 07a4415..f51547e 100644 --- a/10-custom-action/readme.md +++ b/10-custom-action/readme.md @@ -16,11 +16,15 @@ inputs: description: 'Commodity (gold or silver)' required: true default: 'gold' + currency: # id of input + description: 'Currency (USD or EUR)' + required: true + default: 'USD' outputs: price: # id of output description: 'The current price of the select commodity' runs: - using: 'node16' + using: 'node20' main: 'index.js' ``` @@ -33,30 +37,46 @@ npm i @actions/core @actions/github * Write the action code. Create a new file called `index.js`. ```js -const core = require('@actions/core'); -const github = require('@actions/github'); +import { getInput, setOutput, setFailed } from '@actions/core'; +import { context } from '@actions/github'; try { // `commodity` input defined in action metadata file - const commodity = core.getInput('commodity'); - console.log(`Getting current ${commodity} price per ounce...`); + const commodity = getInput('commodity'); + + // Check if input is valid + if (commodity.toLowerCase() !== 'gold' && commodity.toLowerCase() !== 'silver') { + throw new Error(`Commodity ${commodity} is not valid`); + } + + // `currency` input defined in action metadata file + const currency = getInput('currency'); + + // Check if input is valid + if (currency.toUpperCase() !== 'USD' && currency.toUpperCase() !== 'EUR') { + throw new Error(`Currency ${currency} is not valid`); + } + + console.log(`Getting current ${commodity} price per ounce in ${currency}...`); let price = 0; + if (commodity.toLowerCase() === 'gold') { - price = 2019.80; // https://www.bullionvault.com/gold-price-chart.do - console.log(`Current gold price per ounce: ${price} USD`); - } else if (commodity.toLowerCase() === 'silver') { - price = 24.07; // https://www.bullionvault.com/silver-price-chart.do - console.log(`Current silver price per ounce: ${price} USD`); + // https://www.bullionvault.com/gold-price-chart.do + price = currency.toUpperCase() === 'USD' ? 2652.84 : 2524.52; + } else { + // https://www.bullionvault.com/silver-price-chart.do + price = currency.toUpperCase() === 'USD' ? 30.58 : 29.10; } + console.log(`Current ${commodity} price per ounce: ${price} ${currency}`); // `price` output defined in action metadata file - core.setOutput('price', price); + setOutput('price', price); // Get the JSON webhook payload for the event that triggered the workflow - const payload = JSON.stringify(github.context.payload, undefined, 2) + const payload = JSON.stringify(context.payload, undefined, 2) console.log(`The event payload: ${payload}`); } catch (error) { - core.setFailed(error.message); + setFailed(error.message); } ``` @@ -66,7 +86,7 @@ try { ```bash git add action.yml index.js node_modules/* package.json package-lock.json README.md git commit -m "Action ready for release" -git tag -a -m "Get commodity price action initial release" v1.0.1 +git tag -a -m "Get commodity price action initial release" v1.0.0 git push --follow-tags ``` @@ -97,9 +117,10 @@ jobs: steps: - name: Get commodity price step - uses: jtrillo/get-commodity-price-action@v1.0.1 + uses: jtrillo/get-commodity-price-action@v1.0.0 with: - commodity: 'silver' + commodity: silver + currency: EUR ``` ```bash @@ -110,6 +131,43 @@ git push * Now we can fire the workflow and check if the custom action we have created works. +* We could also use matrix strategy to try out several combinations at the same time. + +> Some not valid values could be added in the matrix too. Custom action will throw an error. + +```diff +name: Workflow to test the custom action + +on: + workflow_dispatch: + +jobs: + get_commodity_price: + runs-on: ubuntu-latest ++ strategy: ++ matrix: ++ commodity: ["gold", "SILVER"] ++ currency: ["USD", "eur"] + + steps: + - name: Get commodity price + id: commodity_price + uses: jtrillo/get-commodity-price-action@v1.0.0 + with: +- commodity: silver ++ commodity: ${{ matrix.commodity }} +- currency: EUR ++ currency: ${{ matrix.currency }} +``` + +* Push the new changes and fire the workflow again + +```bash +git add . +git commit -m "added matrix strategy to test custom action" +git push +``` + * We can also make use of action's output in a different step. ```diff @@ -121,16 +179,21 @@ on: jobs: get_commodity_price: runs-on: ubuntu-latest + strategy: + matrix: + commodity: ["gold", "silver"] + currency: ["USD", "EUR"] steps: - name: Get commodity price step + id: commodity_price uses: jtrillo/get-commodity-price@v1.0.0 with: - commodity: 'silver' + commodity: ${{ matrix.commodity }} + currency: ${{ matrix.currency }} + # Use the output from `commodity_price` step + - name: Get the output price -+ run: echo "Price per ounce is ${{ steps.commodity_price.outputs.price }} USD" ++ run: echo "Price per ounce is ${{ steps.commodity_price.outputs.price }} ${{ matrix.currency }}" ``` * Push the new changes and fire the workflow again