{
    "componentChunkName": "component---src-templates-blog-post-tsx",
    "path": "/blog/2022-12-12-dynamic-github-action-jobs/",
    "result": {"data":{"blogPost":{"title":"Dynamic Github Action Jobs","slug":"/blog/2022-12-12-dynamic-github-action-jobs/","authorNodes":[{"name":"Jacob Bolda","slug":"/people/jacob-bolda/"}],"markdown":{"html":"<h1 id=\"dynamic-github-action-jobs\" style=\"position:relative;\"><a href=\"#dynamic-github-action-jobs\" aria-label=\"dynamic github action jobs permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Dynamic Github Action Jobs</h1>\n<p>Github Actions is a CI/CD platform integrated directly with your github.com repository. It is a popular choice to build and test your code within a pull request or on a commit to a branch. It also has many other options for triggering a workflow.</p>\n<p>As a team embraces their Github Action implementation the CI+CD run times can explode. This leads a team towards tuning their implementation and only running the specific jobs that are required. Naturally in this evolution, the need to build workflow context dynamically exponentially increases. Keeping all of the context in a workflow file becomes quite difficult to maintain, and is limited by static values.</p>\n<p>The workflows are written in YAML. The event(s) that triggers the workflow are specified with the <code class=\"language-text\">on</code> parameter. Each workflow file has one or many <code class=\"language-text\">jobs</code> with a named parameter, in this example <code class=\"language-text\">run-linters</code>. Each job has a list of <code class=\"language-text\">steps</code> which is the actions or commands that are run. This job has three steps which install the dependencies and run the lint script.</p>\n<div class=\"gatsby-highlight\" data-language=\"yml\"><pre class=\"language-yml\"><code class=\"language-yml\"><span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Lint\n\n<span class=\"token key atrule\">on</span><span class=\"token punctuation\">:</span> push\n\n<span class=\"token key atrule\">jobs</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">run-linters</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Lint\n    <span class=\"token key atrule\">runs-on</span><span class=\"token punctuation\">:</span> ubuntu<span class=\"token punctuation\">-</span>latest\n\n    <span class=\"token key atrule\">steps</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> actions/checkout@v2\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> volta<span class=\"token punctuation\">-</span>cli/action@v1\n\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Install Node.js dependencies\n        <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> npm ci\n\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Run linters\n        <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> npm run lint</code></pre></div>\n<p>Within a GitHub action workflow, steps can pass data to the following steps. A step can set output, and the following steps can use that it as a variable. In the example below, the named step, <code class=\"language-text\">Get Version</code>, sets an output value. The <code class=\"language-text\">::</code> syntax is picked up by the Github Action runner when output to stdout, ie. the terminal. More information about this syntax can be found at the <a href=\"https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">workflow commands</a> Github docs. The <code class=\"language-text\">id</code> is also set in this step as <code class=\"language-text\">vars</code>. This is important as this is the variable the following steps use to retrieve the value. The step named <code class=\"language-text\">Build</code> immediately following sets an env using <code class=\"language-text\">steps.vars</code> to refer to the <code class=\"language-text\">Get Version</code> step.</p>\n<div class=\"gatsby-highlight\" data-language=\"yml\"><pre class=\"language-yml\"><code class=\"language-yml\"><span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Release to NPM\n<span class=\"token key atrule\">on</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span>push<span class=\"token punctuation\">]</span>\n\n<span class=\"token key atrule\">jobs</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">release</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">runs-on</span><span class=\"token punctuation\">:</span> ubuntu<span class=\"token punctuation\">-</span>latest\n\n    <span class=\"token key atrule\">steps</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> checkout\n        <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> actions/checkout@v3\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> setup deno\n        <span class=\"token comment\"># uses: denoland/setup-deno@v1</span>\n        <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> denoland/setup<span class=\"token punctuation\">-</span>deno@004814556e37c54a2f6e31384c9e18e983317366\n        <span class=\"token key atrule\">with</span><span class=\"token punctuation\">:</span>\n          <span class=\"token key atrule\">deno-version</span><span class=\"token punctuation\">:</span> v1.x\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Get Version\n        <span class=\"token key atrule\">id</span><span class=\"token punctuation\">:</span> vars\n        <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> echo <span class=\"token punctuation\">:</span><span class=\"token punctuation\">:</span>set<span class=\"token punctuation\">-</span>output name=version<span class=\"token punctuation\">:</span><span class=\"token punctuation\">:</span>$(echo $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span>github.ref_name<span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span> <span class=\"token punctuation\">|</span> sed 's/^v//')\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Build\n        <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> deno task build<span class=\"token punctuation\">:</span>npm $NPM_VERSION\n        <span class=\"token key atrule\">env</span><span class=\"token punctuation\">:</span>\n          <span class=\"token key atrule\">NPM_VERSION</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span>steps.vars.outputs.version<span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Publish\n        <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> npm publish <span class=\"token punctuation\">-</span><span class=\"token punctuation\">-</span>access=public\n        <span class=\"token key atrule\">working-directory</span><span class=\"token punctuation\">:</span> ./build/npm\n        <span class=\"token key atrule\">env</span><span class=\"token punctuation\">:</span>\n          <span class=\"token key atrule\">NODE_AUTH_TOKEN</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span>secrets.NPM_TOKEN<span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span></code></pre></div>\n<p>This concept also applies to jobs. A step can set an output, and the job can take that step output and set it as a job output. This allows you to do actions dependent on previous job output. \"How might this be useful though?\", you ask.</p>\n<p>One way to level up your jobs is a parameter called <code class=\"language-text\">strategy</code>. It allows you to pass an argument, <code class=\"language-text\">matrix</code>, to run multiple jobs in parallel. This takes an array or multiple arrays of arguments which will then be combined into a number of jobs. In the example below, we specify a <code class=\"language-text\">matrix</code> with two sets of values. The <code class=\"language-text\">platform</code> is referenced in the <code class=\"language-text\">runs-on</code> and <code class=\"language-text\">node-version</code> is used by the <code class=\"language-text\">volta-cli/action</code>. This specific array will produce a job set of consisting of six jobs run in parallel, e.g. <code class=\"language-text\">ubuntu-latest</code> + <code class=\"language-text\">nodejs@16</code>.</p>\n<div class=\"gatsby-highlight\" data-language=\"yml\"><pre class=\"language-yml\"><code class=\"language-yml\"><span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Test\n<span class=\"token key atrule\">on</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span>pull_request<span class=\"token punctuation\">]</span>\n\n<span class=\"token key atrule\">jobs</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">test</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">runs-on</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> matrix.platform <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n    <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> matrix.platform <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span> test node@$<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> matrix.node<span class=\"token punctuation\">-</span>version <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n    <span class=\"token key atrule\">strategy</span><span class=\"token punctuation\">:</span>\n      <span class=\"token key atrule\">fail-fast</span><span class=\"token punctuation\">:</span> <span class=\"token boolean important\">false</span>\n      <span class=\"token key atrule\">matrix</span><span class=\"token punctuation\">:</span>\n        <span class=\"token key atrule\">platform</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span>ubuntu<span class=\"token punctuation\">-</span>latest<span class=\"token punctuation\">,</span> windows<span class=\"token punctuation\">-</span>latest<span class=\"token punctuation\">]</span>\n        <span class=\"token key atrule\">node-version</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">'14'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'16'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'18'</span><span class=\"token punctuation\">]</span>\n    <span class=\"token key atrule\">steps</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> actions<span class=\"token punctuation\">-</span>rs/toolchain@v1\n        <span class=\"token key atrule\">with</span><span class=\"token punctuation\">:</span>\n          <span class=\"token key atrule\">toolchain</span><span class=\"token punctuation\">:</span> <span class=\"token number\">1.63</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> actions/checkout@v2\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> yarn install\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> yarn build\n      <span class=\"token comment\"># specifically running this after build</span>\n      <span class=\"token comment\"># using the n-api, the default node on github actions</span>\n      <span class=\"token comment\"># should build a .node that works in any of the node</span>\n      <span class=\"token comment\"># versions that we have specified in the matrix</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> volta<span class=\"token punctuation\">-</span>cli/action@v1\n        <span class=\"token key atrule\">with</span><span class=\"token punctuation\">:</span>\n          <span class=\"token key atrule\">node-version</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> matrix.node<span class=\"token punctuation\">-</span>version <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n          <span class=\"token key atrule\">yarn-version</span><span class=\"token punctuation\">:</span> 1.22.19\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> yarn test</code></pre></div>\n<p>As an example in a monorepo, you may have ten different packages. You can give it an array which includes a list of each of the packages and an array of nodejs versions. The jobs that would be then created based on these two matrix inputs and would produce 20 jobs, 10 services by the two nodejs versions. It enables parallel processing and may increase the speed of your CI runs. This can also increase the transparency as you see the separate jobs in the status section of a pull request.</p>\n<p>![[dynamic-github-action-jobs-status-checks.png]]</p>\n<p>The downside of the static array of values is having multiple sources of truth. If you have multiple workflows, this matrix needs to be replicated across each and kept in sync. The matrix accepts an argument called <code class=\"language-text\">include</code> which allows you to specify additional jobs over and above the jobs derived from the matrix arrays or additional metadata each job(s).</p>\n<p>The <a href=\"https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrixinclude\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Github Actions documentation</a> has an advanced example. This following example would add a <code class=\"language-text\">matrix.color</code> to each job with a value of <code class=\"language-text\">green</code>. There would be four total jobs each with a <code class=\"language-text\">{ fruit, animal, color }</code>.</p>\n<div class=\"gatsby-highlight\" data-language=\"yml\"><pre class=\"language-yml\"><code class=\"language-yml\"><span class=\"token key atrule\">strategy</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">matrix</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">fruit</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span>apple<span class=\"token punctuation\">,</span> pear<span class=\"token punctuation\">]</span>\n    <span class=\"token key atrule\">animal</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span>cat<span class=\"token punctuation\">,</span> dog<span class=\"token punctuation\">]</span>\n    <span class=\"token key atrule\">include</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">color</span><span class=\"token punctuation\">:</span> green</code></pre></div>\n<p>A <code class=\"language-text\">strategy</code> is able to specify only an <code class=\"language-text\">include</code>. The following example create two jobs, each with <code class=\"language-text\">{ color, fruit }</code>. The first job would be <code class=\"language-text\">{ color: \"green\", fruit: \"apple\" }</code> and the second job would be <code class=\"language-text\">{ color: \"blue\", fruit: \"pear\" }</code>.</p>\n<div class=\"gatsby-highlight\" data-language=\"yml\"><pre class=\"language-yml\"><code class=\"language-yml\"><span class=\"token key atrule\">strategy</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">matrix</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">include</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">color</span><span class=\"token punctuation\">:</span> green\n        <span class=\"token key atrule\">fruit</span><span class=\"token punctuation\">:</span> apple\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">color</span><span class=\"token punctuation\">:</span> blue\n        <span class=\"token key atrule\">fruit</span><span class=\"token punctuation\">:</span> pear</code></pre></div>\n<p>This is where a few built-in functions within GitHub Actions can become immensely useful using output passed between jobs and a function to convert that output into a matrix value. We can use this to our advantage; compute the array of jobs ourselves and pass it to this <code class=\"language-text\">include</code> argument.</p>\n<p>Through the <a href=\"https://docs.github.com/en/actions/learn-github-actions/expressions#tojson\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><code class=\"language-text\">toJSON</code> function</a>, the <code class=\"language-text\">matrix</code> argument can receive a stringified object. The <code class=\"language-text\">toJSON</code> function takes a string and outputs a value that the job is able to use. This allows us to programmatically, in a previous job, determine the matrix of jobs to run.</p>\n<p>It is particularly powerful in monorepos where you can use static analysis to determine which code paths have changed, and only test those. With the monorepo helper Nx, for example, we can run a function which describes the code that has been affected by a change in pull request, nx affected. Using this output, we can parse it into an object and pass it into our matrix.</p>\n<p>The object can be created however it is most convenient, as long as it can be output to the terminal. For an example using nodejs, we would log out the output of a <code class=\"language-text\">JSON.stringify()</code>. The script shown below, when run, would create three jobs with a <code class=\"language-text\">matrix.package</code> value being passed into each.</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> matrixList <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">'package-a'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'package-b'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'package-c'</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">pkg</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">package</span><span class=\"token operator\">:</span> pkg<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> includeStatement <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span> <span class=\"token literal-property property\">include</span><span class=\"token operator\">:</span> matrixList <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">::set-output name=matrix::</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span><span class=\"token constant\">JSON</span><span class=\"token punctuation\">.</span><span class=\"token function\">stringify</span><span class=\"token punctuation\">(</span>includeStatement<span class=\"token punctuation\">)</span><span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>We use this script in the following workflow. It has two jobs: <code class=\"language-text\">generate-matrix</code> and <code class=\"language-text\">test</code>. In the <code class=\"language-text\">generate-matrix</code> job, we run the <code class=\"language-text\">matrix-script.js</code> shown above which logs the string to <code class=\"language-text\">stdout</code>. The <code class=\"language-text\">::set-output name=matrix::</code> is a special function within Github Actions which tells the runner to set this as an output value for this step. We assign it to a variable by setting the <code class=\"language-text\">id: set-matrix</code>. Finally, we can set it as an output for the whole job by specifying it in <code class=\"language-text\">outputs</code>.</p>\n<p>The following job can use the output from the previous job by setting the job ID in the <code class=\"language-text\">needs</code> property. The <code class=\"language-text\">test</code> job uses <code class=\"language-text\">needs: [generate-matrix]</code> and can use the values via the <code class=\"language-text\">needs</code> elsewhere in the job. It has a conditional which determines if the job should run or skip with <code class=\"language-text\">if: needs.generate-matrix.outputs.matrix != ''</code>. It is referenced with the <code class=\"language-text\">needs.&lt;job id>.outputs.&lt;property from job id outputs></code>.</p>\n<div class=\"gatsby-highlight\" data-language=\"yml\"><pre class=\"language-yml\"><code class=\"language-yml\"><span class=\"token key atrule\">jobs</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">generate-matrix</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Generate Job Matrix\n    <span class=\"token key atrule\">runs-on</span><span class=\"token punctuation\">:</span> ubuntu<span class=\"token punctuation\">-</span>latest\n    <span class=\"token key atrule\">outputs</span><span class=\"token punctuation\">:</span>\n      <span class=\"token key atrule\">matrix</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> steps.set<span class=\"token punctuation\">-</span>matrix.outputs.matrix <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n    <span class=\"token key atrule\">steps</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> actions/checkout@v3\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> yarn\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> node ./matrix<span class=\"token punctuation\">-</span>script.js\n        <span class=\"token key atrule\">id</span><span class=\"token punctuation\">:</span> set<span class=\"token punctuation\">-</span>matrix\n\n  <span class=\"token key atrule\">test</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Check $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> matrix.nickname <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n    <span class=\"token key atrule\">runs-on</span><span class=\"token punctuation\">:</span> ubuntu<span class=\"token punctuation\">-</span>latest\n    <span class=\"token key atrule\">if</span><span class=\"token punctuation\">:</span> needs.generate<span class=\"token punctuation\">-</span>matrix.outputs.matrix <span class=\"token tag\">!=</span> ''\n    <span class=\"token key atrule\">needs</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span>generate<span class=\"token punctuation\">-</span>matrix<span class=\"token punctuation\">]</span>\n    <span class=\"token key atrule\">strategy</span><span class=\"token punctuation\">:</span>\n      <span class=\"token key atrule\">fail-fast</span><span class=\"token punctuation\">:</span> <span class=\"token boolean important\">false</span>\n      <span class=\"token key atrule\">max-parallel</span><span class=\"token punctuation\">:</span> <span class=\"token number\">6</span>\n      <span class=\"token key atrule\">matrix</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span>fromJSON(needs.generate<span class=\"token punctuation\">-</span>matrix.outputs.matrix)<span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n\n    <span class=\"token key atrule\">steps</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> actions/checkout@v3\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> yarn\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> yarn workspace $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> matrix.package <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span> test</code></pre></div>\n<p>Building on this functionality, we can level up our script, <code class=\"language-text\">matrix-script.js</code> with static code analysis. As we mentioned previously, <code class=\"language-text\">Nx</code> has a command called <code class=\"language-text\">nx affected</code>. We can wire this into our script similar to the following. This would return a list of <code class=\"language-text\">affected</code> packages. (Note this is a generator function expected to run within an <a href=\"https://frontside.com/effection\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><code class=\"language-text\">effection</code> context</a>, so you may not be able to copy and paste this directly.) This would give you a dynamic array that can be used in the script rather than directly hardcoding the array as <code class=\"language-text\">['package-a', 'package-b', 'package-c']</code>.</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span><span class=\"token operator\">*</span> <span class=\"token function\">affectedList</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> nxBase <span class=\"token operator\">=</span> process<span class=\"token punctuation\">.</span>env<span class=\"token punctuation\">.</span><span class=\"token constant\">NX_BASE</span> <span class=\"token operator\">??</span> <span class=\"token string\">'main'</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> nxHead <span class=\"token operator\">=</span> process<span class=\"token punctuation\">.</span>env<span class=\"token punctuation\">.</span><span class=\"token constant\">NX_HEAD</span> <span class=\"token operator\">??</span> <span class=\"token string\">'HEAD'</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token comment\">// if you want to check it locally with uncommitted changes</span>\n  <span class=\"token comment\">// const command = `nx affected:libs --plain --uncommitted`;</span>\n  <span class=\"token keyword\">const</span> command <span class=\"token operator\">=</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">nx affected:libs --plain --base=</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>nxBase<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> --head=</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>nxHead<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> stdout<span class=\"token punctuation\">,</span> stderr<span class=\"token punctuation\">,</span> code <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">yield</span> <span class=\"token function\">exec</span><span class=\"token punctuation\">(</span>command<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token literal-property property\">cwd</span><span class=\"token operator\">:</span> <span class=\"token string\">'..'</span><span class=\"token punctuation\">,</span>\n    <span class=\"token literal-property property\">env</span><span class=\"token operator\">:</span> process<span class=\"token punctuation\">.</span>env<span class=\"token punctuation\">,</span>\n    <span class=\"token literal-property property\">shell</span><span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">join</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">error</span><span class=\"token punctuation\">(</span>stderr<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>code <span class=\"token operator\">!==</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">exited with code </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>code<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> affected <span class=\"token operator\">=</span> stdout\n    <span class=\"token punctuation\">.</span><span class=\"token function\">split</span><span class=\"token punctuation\">(</span><span class=\"token string\">' '</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">x</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span><span class=\"token function\">trim</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">filter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">x</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span>length <span class=\"token operator\">></span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">join</span><span class=\"token punctuation\">(</span><span class=\"token string\">','</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">return</span> affected<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>With the additional context required to fuel <code class=\"language-text\">nx affected</code>, the following workflow would create the extra context via <code class=\"language-text\">nwql/nx-set-shas</code>, derive the <code class=\"language-text\">nx affected</code>, pipe the generated matrix into <code class=\"language-text\">test</code> job, create a job for each affected package and run the test for the package. The nx command <code class=\"language-text\">yarn nx run ${{ matrix.package}}:test</code> will have the <code class=\"language-text\">${{ matrix.package }}</code> populated with the value passed into each job. The command run for the job with <code class=\"language-text\">{ package: 'package-a' }</code> would in turn be <code class=\"language-text\">yarn nx run package-a:test</code>.</p>\n<div class=\"gatsby-highlight\" data-language=\"yml\"><pre class=\"language-yml\"><code class=\"language-yml\"><span class=\"token key atrule\">jobs</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">generate-matrix</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Generate Job Matrix\n    <span class=\"token key atrule\">runs-on</span><span class=\"token punctuation\">:</span> ubuntu<span class=\"token punctuation\">-</span>latest\n    <span class=\"token key atrule\">outputs</span><span class=\"token punctuation\">:</span>\n      <span class=\"token key atrule\">matrix</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> steps.set<span class=\"token punctuation\">-</span>matrix.outputs.matrix <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n      <span class=\"token key atrule\">nxBase</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> steps.nx<span class=\"token punctuation\">-</span>sha.outputs.base <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n      <span class=\"token key atrule\">nxHead</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> steps.nx<span class=\"token punctuation\">-</span>sha.outputs.head <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n    <span class=\"token key atrule\">steps</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> actions/checkout@v3\n        <span class=\"token key atrule\">with</span><span class=\"token punctuation\">:</span>\n          <span class=\"token comment\"># We need to fetch all branches and commits so that Nx affected has a base to compare against.</span>\n          <span class=\"token key atrule\">fetch-depth</span><span class=\"token punctuation\">:</span> <span class=\"token number\">0</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Derive appropriate SHAs for base and head for `nx affected` commands\n        <span class=\"token key atrule\">id</span><span class=\"token punctuation\">:</span> nx<span class=\"token punctuation\">-</span>sha\n        <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> nrwl/nx<span class=\"token punctuation\">-</span>set<span class=\"token punctuation\">-</span>shas@v2\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> yarn\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> node ./matrix<span class=\"token punctuation\">-</span>script.js\n        <span class=\"token key atrule\">id</span><span class=\"token punctuation\">:</span> set<span class=\"token punctuation\">-</span>matrix\n\n  <span class=\"token key atrule\">test</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Check $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> matrix.nickname <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n    <span class=\"token key atrule\">runs-on</span><span class=\"token punctuation\">:</span> ubuntu<span class=\"token punctuation\">-</span>latest\n    <span class=\"token key atrule\">if</span><span class=\"token punctuation\">:</span> needs.generate<span class=\"token punctuation\">-</span>matrix.outputs.matrix <span class=\"token tag\">!=</span> ''\n    <span class=\"token key atrule\">needs</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span>generate<span class=\"token punctuation\">-</span>matrix<span class=\"token punctuation\">]</span>\n    <span class=\"token key atrule\">strategy</span><span class=\"token punctuation\">:</span>\n      <span class=\"token key atrule\">fail-fast</span><span class=\"token punctuation\">:</span> <span class=\"token boolean important\">false</span>\n      <span class=\"token key atrule\">max-parallel</span><span class=\"token punctuation\">:</span> <span class=\"token number\">6</span>\n      <span class=\"token key atrule\">matrix</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span>fromJSON(needs.generate<span class=\"token punctuation\">-</span>matrix.outputs.matrix)<span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n\n    <span class=\"token key atrule\">steps</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> actions/checkout@v2\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> volta<span class=\"token punctuation\">-</span>cli/action@v1\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> yarn\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> yarn nx run $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> matrix.package <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">:</span>test</code></pre></div>\n<p>With this workflow and creating our own array of jobs, it enables tight control of what and when CI runs. This can save on time and build minutes, as well as increase transparency through the parallel CI runs. With a set of workflows that builds, tests, creates docker files and helm charts and finally deploys, tuning the CI+CD is required to merge more than a single pull request or two a day.</p>\n<p>We can use static code analysis to drive a CI setup that only builds and tests the code paths that have changed. It provides the most feedback in the shortest amount of time. From this, we can enable CD to deploy only the services that have changed into a QA environment and save this metadata for production deploys.</p>\n<p><strong><em>Want to learn more? We have a great community in <a href=\"https://discord.gg/9xfdDYthpF\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Discord</a>; come and say hello! We like to help and discuss all kinds of tech and code topics.</em></strong></p>","frontmatter":{"date":"December 12, 2022","description":"Ever wanted to run parallel jobs in Github Actions with a high level of flexibility? We have, and now you can too!","tags":["github-actions","continuous-delivery"],"img":{"childImageSharp":{"fixed":{"src":"/static/34d3b4cfaf13078c38dafa972164d82f/31987/2022-08-22-dynamic-github-actions-jobs.png"}}}}}}},"pageContext":{"id":"02503eee-29ed-56e0-a616-f4863e2d176c"}},
    "staticQueryHashes": ["1241260443"]}