<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Integralist</title>
    <description>The latest articles on Forem by Integralist (@integralist).</description>
    <link>https://forem.com/integralist</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1643%2F4azx59GT.jpg</url>
      <title>Forem: Integralist</title>
      <link>https://forem.com/integralist</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/integralist"/>
    <language>en</language>
    <item>
      <title>Better Fastly API clients with OpenAPI Generator</title>
      <dc:creator>Integralist</dc:creator>
      <pubDate>Tue, 01 Nov 2022 11:39:39 +0000</pubDate>
      <link>https://forem.com/fastly/better-fastly-api-clients-with-openapi-generator-3lno</link>
      <guid>https://forem.com/fastly/better-fastly-api-clients-with-openapi-generator-3lno</guid>
      <description>&lt;p&gt;The &lt;a href="https://developer.fastly.com/reference/api/" rel="noopener noreferrer"&gt;Fastly API&lt;/a&gt; is huge. We have lots of customers who want to interact with it using their chosen programming language but our small set of manually maintained clients was not sufficient to handle the job of our ever-evolving API. We needed a way to scale up our API client support, and &lt;a href="https://openapis.org" rel="noopener noreferrer"&gt;OpenAPI&lt;/a&gt; was the answer.&lt;/p&gt;

&lt;p&gt;We describe our API using the &lt;a href="https://oai.github.io/Documentation/specification.html" rel="noopener noreferrer"&gt;OpenAPI specification&lt;/a&gt;, which defines a standard, language-agnostic interface. The use of OpenAPI enables us to programmatically interact with the API metadata to produce content, code examples, tools and other valuable resources.&lt;/p&gt;

&lt;p&gt;Here is an example of one of our API endpoints. It highlights how we define a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET" rel="noopener noreferrer"&gt;GET&lt;/a&gt; request and what a successful (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200" rel="noopener noreferrer"&gt;200 OK&lt;/a&gt;) response looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Backend&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A backend is a server identified by IP address or hostname.&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;

&lt;span class="na"&gt;externalDocs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://developer.fastly.com/reference/api/services/backend&lt;/span&gt;

&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://api.fastly.com&lt;/span&gt;

&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;/service/{service_id}/version/{version_id}/backend&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service.yaml#/components/parameters/service_id"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version.yaml#/components/parameters/version_id"&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List backends&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List all backends for a particular service and version.&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;list-backends&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OK&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;array&lt;/span&gt;
                &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/backend_response"&lt;/span&gt;
              &lt;span class="na"&gt;examples&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/examples/backend_response"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HINT&lt;/strong&gt;: To reduce duplication we often put common parameters and values into external files that can be referenced separately, we also do this for large nested sections that can otherwise make it difficult to focus on the critical points of an API’s specification (see &lt;a href="https://swagger.io/docs/specification/using-ref/" rel="noopener noreferrer"&gt;&lt;code&gt;$ref&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem: 700 endpoints, 7 languages
&lt;/h2&gt;

&lt;p&gt;In order to enable customers to access our API within a programmatic environment, we provide &lt;a href="https://developer.fastly.com/reference/api/#clients" rel="noopener noreferrer"&gt;API clients&lt;/a&gt; in multiple languages. Over time we produced multiple API clients, all varying in their level of API coverage, code quality and consistency.&lt;/p&gt;

&lt;p&gt;There were multiple factors that meant our API clients didn’t all receive the care and attention they deserved, and so would suffer from code bugs and generally be out of date with our API. For example, we needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Engineers who knew multiple languages and had enough experience with them to not only be able to write code in these languages but to write &lt;em&gt;idiomatic&lt;/em&gt; code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The resources to continually revisit clients when the API changes, as well as the sheer amount of work to support such a large number of endpoints.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The plan
&lt;/h2&gt;

&lt;p&gt;In 2020 the Developer Relations team took ownership of the API clients and we began devising a plan to make them more complete, consistent, and where possible, idiomatic. This wouldn’t be achievable by maintaining all the clients as separate projects, so the primary focus was to &lt;strong&gt;create and maintain common tooling to enable a broad stable of API clients to keep up with our API&lt;/strong&gt; as it evolves.&lt;/p&gt;

&lt;p&gt;We also aimed to allow our Fastly co-workers to easily contribute to the clients when an API changes, avoiding (a) DevRel having to try and implement all API changes into the clients, and (b) other teams having to learn different standards and structures for a bunch of different API clients in order to ship a feature - neither of which would be scalable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: OpenAPI Generator
&lt;/h2&gt;

&lt;p&gt;To achieve these goals we investigated various options and ultimately decided on using a community-driven fork of the official &lt;a href="https://swagger.io" rel="noopener noreferrer"&gt;Swagger CodeGen&lt;/a&gt; project: &lt;a href="https://openapi-generator.tech" rel="noopener noreferrer"&gt;OpenAPI Generator&lt;/a&gt; (OAG). &lt;/p&gt;

&lt;p&gt;OAG is a template-driven engine that can generate documentation, API clients and server stubs in different languages by parsing your OpenAPI specification.&lt;/p&gt;

&lt;h2&gt;
  
  
  How OAG works
&lt;/h2&gt;

&lt;p&gt;OAG is a &lt;a href="https://www.java.com/" rel="noopener noreferrer"&gt;Java&lt;/a&gt; project that uses &lt;a href="https://mustache.github.io/" rel="noopener noreferrer"&gt;Mustache templates&lt;/a&gt; to configure each &lt;a href="https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources" rel="noopener noreferrer"&gt;supported programming language&lt;/a&gt;. It provides a CLI &lt;a href="https://github.com/OpenAPITools/openapi-generator-cli" rel="noopener noreferrer"&gt;&lt;code&gt;openapi-generator-cli&lt;/code&gt;&lt;/a&gt; that will download the appropriate JAR file and invoke the java executable to run OAG.&lt;/p&gt;

&lt;p&gt;To produce a new API client, we pass &lt;code&gt;openapi-generator-cli&lt;/code&gt; our OpenAPI specification, tell it what language we want to use, and where to output the API client code. Here is a snippet from our Makefile that uses Docker to help get the Java CLI tool configured correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;LOCAL_ROOT&lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;/local
&lt;span class="err"&gt;​​&lt;/span&gt;&lt;span class="nv"&gt;OPENAPI_GENERATOR_CLI_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v5.4.0
&lt;span class="nv"&gt;OAPI_GENERATOR_CLI&lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;PWD&lt;span class="p"&gt;)&lt;/span&gt;:&lt;span class="p"&gt;$(&lt;/span&gt;LOCAL_ROOT&lt;span class="p"&gt;)&lt;/span&gt; openapitools/openapi-generator-cli:&lt;span class="p"&gt;$(&lt;/span&gt;​​OPENAPI_GENERATOR_CLI_TAG&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="err"&gt;$(OAPI_GENERATOR_CLI)&lt;/span&gt; &lt;span class="err"&gt;generate&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
    &lt;span class="err"&gt;--global-property&lt;/span&gt; &lt;span class="nv"&gt;apiTests&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;GENERATE_TESTS_API&lt;span class="p"&gt;)&lt;/span&gt;,modelTests&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;GENERATE_TESTS_MODEL&lt;span class="p"&gt;)&lt;/span&gt;,apiDocs&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;GENERATE_DOCS_API&lt;span class="p"&gt;)&lt;/span&gt;,modelDocs&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;GENERATE_DOCS_MODEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;LOCAL_ROOT&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;BUILT_SCHEMAS_DIR&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;COLLECTION&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;GENERATOR&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;LOCAL_ROOT&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;TEMPLATES_DIR&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;TEMPLATE&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;LOCAL_ROOT&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;TEMPLATES_DIR&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;TEMPLATE&lt;span class="p"&gt;)&lt;/span&gt;.yaml &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;LOCAL_ROOT&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;TEMPLATE&lt;span class="p"&gt;)$(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;COLLECTION_NAME&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"fastly"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,,/&lt;span class="p"&gt;$(&lt;/span&gt;COLLECTION_NAME&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;TEMPLATE&lt;span class="p"&gt;)&lt;/span&gt;/.tmp.&lt;span class="p"&gt;$(&lt;/span&gt;COLLECTION_NAME&lt;span class="p"&gt;)&lt;/span&gt;.log &lt;span class="se"&gt;\&lt;/span&gt;
    2&amp;gt;&lt;span class="p"&gt;$(&lt;/span&gt;TEMPLATE&lt;span class="p"&gt;)&lt;/span&gt;/.tmp.&lt;span class="p"&gt;$(&lt;/span&gt;COLLECTION_NAME&lt;span class="p"&gt;)&lt;/span&gt;.err.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why it’s a good choice
&lt;/h2&gt;

&lt;p&gt;Unlike most other tools we investigated, OAG had good support for multiple languages while also supporting the OpenAPI 3.x specification, which is the major version our own API specification was written in (most other open-source tools at the time only supported 2.x). &lt;/p&gt;

&lt;p&gt;OAG is also a very flexible tool allowing us to control various aspects of the generated code, as well as execute &lt;a href="https://openapi-generator.tech/docs/file-post-processing/" rel="noopener noreferrer"&gt;post-processors&lt;/a&gt; and the ability to define &lt;a href="https://openapi-generator.tech/docs/customization/#custom-generator-and-template" rel="noopener noreferrer"&gt;custom generator templates&lt;/a&gt; if none of the built-in templates fit our requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  What languages we chose and why
&lt;/h2&gt;

&lt;p&gt;Prior to this initiative we had five API clients, so it was important that whatever process we came up with, would support at least &lt;a href="https://rubygems.org/gems/fastly" rel="noopener noreferrer"&gt;Ruby&lt;/a&gt;, &lt;a href="https://pypi.org/project/fastly/1.0.0a2/#history" rel="noopener noreferrer"&gt;Python&lt;/a&gt;, &lt;a href="https://packagist.org/packages/fastly/fastly" rel="noopener noreferrer"&gt;PHP&lt;/a&gt;, &lt;a href="https://github.com/fastly/fastly-perl/tree/v2-generated-alpha" rel="noopener noreferrer"&gt;Perl&lt;/a&gt; and &lt;a href="https://pkg.go.dev/github.com/fastly/go-fastly/v6" rel="noopener noreferrer"&gt;Go&lt;/a&gt;*.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;* This is our hand-coded Go client - the OAG version is still in the works.&lt;small&gt;&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;On top of that list, we also wanted to produce greenfield &lt;a href="https://www.npmjs.com/package/fastly" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt; and &lt;a href="https://crates.io/crates/fastly-api" rel="noopener noreferrer"&gt;Rust&lt;/a&gt; clients as these are popular languages our customers are using.&lt;/p&gt;

&lt;p&gt;Although OAG was doing the hard work of generating API clients for lots of different languages and keeping them up-to-date with our API specifications, as you’ll see, this wasn’t a panacea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Difficulties and solutions
&lt;/h2&gt;

&lt;p&gt;There were a few issues we encountered. The first was we needed a custom build process to manage a large number of OpenAPI specification files (at the time we had approximately 120 and it was growing).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;openapi-generator-cli&lt;/code&gt; tool could only process one specification file at a time, which meant we needed to first aggregate our specifications into a single file (with the help of &lt;a href="https://redocly.com/docs/cli/" rel="noopener noreferrer"&gt;redocly&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;To ensure we would be able to run the code-generation process within our &lt;a href="https://en.wikipedia.org/wiki/Continuous_integration" rel="noopener noreferrer"&gt;Continuous Integration&lt;/a&gt; (CI) pipeline, we needed to use a &lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;Dockerised&lt;/a&gt; version of &lt;code&gt;openapi-generator-cli&lt;/code&gt; (the coordination of which was a complex process). See the next section on how we auto-regenerate clients on spec change.&lt;/p&gt;

&lt;p&gt;Another challenge we encountered was the quality of the community-driven language templates. For the most part, they worked but they didn’t appear to be well maintained over time, nor did every feature in our OpenAPI specification have a corresponding implementation in the language templates. In some cases, the language templates didn’t support the latest programming language version, and the generated documentation wasn’t as good as we needed it to be.&lt;/p&gt;

&lt;p&gt;Additionally, the quality of our OpenAPI specification files would cause problems in the generator, by either triggering runtime errors in the underlying Java parser or in the generated code itself due to incompatibility with the language templates.&lt;/p&gt;

&lt;p&gt;Each of these issues would result in hard-to-debug compilation errors within the code-generation pipeline, and so we would have to figure out which of the three potential layers the issue was stemming from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Fastly OpenAPI specification documents.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources" rel="noopener noreferrer"&gt;openapi-generator mustache templates&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages" rel="noopener noreferrer"&gt;openapi-generator language parsers&lt;/a&gt; (written in Java).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sometimes we would tweak the OpenAPI specification such that the generator’s language parser would take a code path that didn’t trigger a Java error, while solving issues with the language templates would mean having to manually copy the source templates and modify them to suit our needs (this was made possible thanks to OAG supporting custom template files). &lt;/p&gt;

&lt;p&gt;Essentially we had to iterate on both ends of the integration, a bit like trying to fit a square peg into a round hole, then gradually changing the shape of both the peg and the hole until they fit.&lt;/p&gt;

&lt;p&gt;Whenever possible we would push fixes upstream to the open-source &lt;a href="https://github.com/OpenAPITools/openapi-generator" rel="noopener noreferrer"&gt;openapi-generator&lt;/a&gt; project so the wider community could benefit. This was our small way to say thank you to those who came before us and who provided us with these great tools to begin with.&lt;/p&gt;

&lt;p&gt;We also did a lot of work to restructure the generated documentation (which to be fair was already thousands of times more detailed than what we had in our original hand coded API clients) to make it much easier for users to consume and understand the API client interface.&lt;/p&gt;

&lt;p&gt;In some cases, we were unable to resolve the incompatibility so to help unblock ourselves we implemented a custom &lt;a href="https://swagger.io/docs/specification/openapi-extensions/" rel="noopener noreferrer"&gt;OpenAPI extension&lt;/a&gt; that integrates with our CI pipeline and enables us to exclude API endpoints at a very granular level by specifying if the exclusion should be for all endpoints within a specification file or a specific sub-section. We can also exclude all clients or just those for specific languages. &lt;/p&gt;

&lt;p&gt;Examples of this look like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;x-fastly-preprocess-exclude&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api-client&lt;/span&gt;  &lt;span class="c1"&gt;# exclude all clients&lt;/span&gt;

&lt;span class="na"&gt;x-fastly-preprocess-exclude&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fastly-rust&lt;/span&gt; &lt;span class="c1"&gt;# exclude the rust client&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fastly-js&lt;/span&gt;   &lt;span class="c1"&gt;# exclude the javascript client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Alpha releases
&lt;/h2&gt;

&lt;p&gt;The next step after successfully generating an API client was to push the code to the relevant public GitHub repository and then publish the code to its module registry (e.g. &lt;a href="https://crates.io" rel="noopener noreferrer"&gt;crates.io&lt;/a&gt; for Rust, &lt;a href="https://www.npmjs.com" rel="noopener noreferrer"&gt;npmjs.com&lt;/a&gt; for JavaScript, &lt;a href="https://pypi.org" rel="noopener noreferrer"&gt;pypi.org&lt;/a&gt; for Python etc).&lt;/p&gt;

&lt;p&gt;We selected a target group of customers and internal stakeholders to help test out the new API clients and report any bugs in the code, invalid documentation, or missing functionality. There were a few minor issues reported but otherwise, the generated API clients were working pretty well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-regenerating the clients on spec change
&lt;/h2&gt;

&lt;p&gt;The last piece of the puzzle was the ability to automatically regenerate an API client when any OpenAPI specification files were modified. We achieved this by adding two new CI pipeline flows across two separate repositories that would coordinate with each other.&lt;/p&gt;

&lt;p&gt;At a high level, this looked like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4id0vi45g66qwe85kg6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4id0vi45g66qwe85kg6.png" alt="Fastly's CI pipeline for generating API clients"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HINT&lt;/strong&gt;: We use &lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; for our CI/CD. You can learn more about it by reading our blog post “&lt;a href="https://dev.to/fastly/how-fastly-manages-its-software-with-github-actions-3p0i"&gt;How Fastly deploys Gatsby CMS websites to GCS using GitHub Actions&lt;/a&gt;”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We wrote a CI job within the GitHub repository containing our OpenAPI specification files. That job runs whenever changes are merged into the mainline branch and starts by validating and combining the specs.&lt;/p&gt;

&lt;p&gt;Of course, when the API definition changes, it makes sense to regenerate all the API clients; not just because we want to keep our public clients up to date, but also because as part of reviewing open PRs, it's great to be able to grab a client and use it to actually test out your new API before we integrate its definition into our official OpenAPI spec.&lt;/p&gt;

&lt;p&gt;GitHub Actions turns out to have an incredibly useful hook called &lt;a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch" rel="noopener noreferrer"&gt;workflow_dispatch&lt;/a&gt;, which allows a change in one repo to trigger an action in a different repo! We define a step that uses a conditional check to prevent it from being executed if there were no specification changes, otherwise, it triggers a workflow file within the GitHub repository that contains our API client generator logic/build process. &lt;/p&gt;

&lt;p&gt;The CI job defined in the API client generator repo is configured with the &lt;code&gt;workflow_dispatch&lt;/code&gt; event. It receives the branch and commit SHA from the OpenAPI specification repo, and it posts status updates back to the OpenAPI repo informing it of the build progress.&lt;/p&gt;

&lt;p&gt;The next step in the API client generator CI job checks out the appropriate branch of the OpenAPI specification repo and uses it to build all the API clients.&lt;/p&gt;

&lt;p&gt;Finally, the CI job sends a status update to the OpenAPI specification repo letting it know where the generated API clients were uploaded to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Auto-generating our API clients has been a long project. The work started with the initial move to document our API using OpenAPI specifications. This enabled us to make a much better API reference on our &lt;a href="https://developer.fastly.com" rel="noopener noreferrer"&gt;Developer Hub&lt;/a&gt; that consumes those specification files and produces content from them. &lt;/p&gt;

&lt;p&gt;The code generation itself hasn’t been easy: we’ve had to balance the complexity of coordinating changes across multiple repositories while handling errors over multiple layers, and producing API clients that are not only idiomatic to the language they’re built in but also correct and accessible.&lt;/p&gt;

&lt;p&gt;That said, for us, this was a task that we needed to take to ensure we were providing our customers with the best possible tools and experience, and enabling them to interact with the Fastly platform more efficiently.&lt;/p&gt;

&lt;p&gt;We’re now able to display code examples for each supported language in our API documentation, making it easy for customers to get started using our APIs. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4y4zh48ysnlner2xi6m8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4y4zh48ysnlner2xi6m8.png" alt="Fastly API code examples"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any time the API definition is improved, we end up improving the quality of the API documentation. Lastly, this work has encouraged our own developers to engage with the API definition.&lt;/p&gt;

&lt;p&gt;Take a look at &lt;a href="https://developer.fastly.com/reference/api/#clients" rel="noopener noreferrer"&gt;the clients&lt;/a&gt; and let us know what you think. We love hearing how customers are using our tools and our platform.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How Fastly deploys Gatsby CMS websites to GCS using GitHub Actions</title>
      <dc:creator>Integralist</dc:creator>
      <pubDate>Wed, 31 Aug 2022 14:40:30 +0000</pubDate>
      <link>https://forem.com/fastly/how-fastly-manages-its-software-with-github-actions-3p0i</link>
      <guid>https://forem.com/fastly/how-fastly-manages-its-software-with-github-actions-3p0i</guid>
      <description>&lt;p&gt;Developing and releasing software is a complex process. The most common solution is to use an automated system for testing, validating, and publishing software. &lt;/p&gt;

&lt;p&gt;We’re going to look at what this means, how this is typically implemented, and learn about a platform that helps us to create these software pipelines. We’ll see the features in context by digging into how Fastly has implemented them for one of its own services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here is a breakdown of the content&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tools and terminology&lt;/li&gt;
&lt;li&gt;Coordinating complex pipelines&lt;/li&gt;
&lt;li&gt;The 10,000-foot view&lt;/li&gt;
&lt;li&gt;Storing your workflows&lt;/li&gt;
&lt;li&gt;Handling events&lt;/li&gt;
&lt;li&gt;A ‘Hello World’ job&lt;/li&gt;
&lt;li&gt;Validating against a matrix&lt;/li&gt;
&lt;li&gt;Third-party actions&lt;/li&gt;
&lt;li&gt;Accessing contextual data&lt;/li&gt;
&lt;li&gt;Defining environment variables&lt;/li&gt;
&lt;li&gt;Managing secrets&lt;/li&gt;
&lt;li&gt;Flow control&lt;/li&gt;
&lt;li&gt;Persisting data&lt;/li&gt;
&lt;li&gt;Reusable workflows&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tools and terminology
&lt;/h2&gt;

&lt;p&gt;First, let’s understand what it is we’re going to be talking about: CI/CD.&lt;/p&gt;

&lt;p&gt;CI (&lt;a href="https://en.wikipedia.org/wiki/Continuous_integration"&gt;Continuous Integration&lt;/a&gt;) is the process of automating the merging of new or updated code into an existing code base and utilising supplementary tools, such as &lt;a href="https://en.wikipedia.org/wiki/Lint_(software)"&gt;code linters&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Unit_testing"&gt;unit tests&lt;/a&gt;, to help make the integration as correct as possible.&lt;/p&gt;

&lt;p&gt;CD (&lt;a href="https://en.wikipedia.org/wiki/Continuous_deployment"&gt;Continuous Deployment&lt;/a&gt;) is an extension of CI, focused on automating the deployment of a piece of software once the CI process has been completed successfully.&lt;/p&gt;

&lt;p&gt;There are many CI/CD solutions available, such as &lt;a href="https://www.jenkins.io"&gt;Jenkins&lt;/a&gt; and &lt;a href="https://www.travis-ci.com"&gt;Travis CI&lt;/a&gt; (both of which Fastly has used in the past) but here I’m going to be talking about &lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt;, which has proven to be a powerful and flexible tool that has enabled us to simplify our content publishing pipeline.&lt;/p&gt;

&lt;p&gt;The GitHub Actions platform is managed by &lt;a href="https://github.com"&gt;GitHub&lt;/a&gt; (the largest online collaboration platform where developers and companies build and maintain their software). This means GitHub Actions exist alongside where our code lives, and this can enable better integration with our internal repositories compared to external solutions such as Jenkins/Travis.&lt;/p&gt;

&lt;p&gt;Different CI/CD platforms use different terminology to describe their service model. With GitHub Actions, everything starts with a ‘workflow’. A workflow is a &lt;a href="https://en.wikipedia.org/wiki/YAML"&gt;YAML&lt;/a&gt; configuration file that consists of&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Jobs&lt;/strong&gt;: a job represents a collection of 'steps'.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steps&lt;/strong&gt;: each ‘step’ executes logic to achieve some goal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Events&lt;/strong&gt;: an event determines when your jobs are run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runner&lt;/strong&gt;: a &lt;a href="https://en.wikipedia.org/wiki/Virtual_machine"&gt;virtual machine&lt;/a&gt; that runs your jobs. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub provides &lt;a href="https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions"&gt;a nice visualisation&lt;/a&gt; of this structure...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GabvKJ0E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/10xq2vysmybwrtug7g8b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GabvKJ0E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/10xq2vysmybwrtug7g8b.png" alt="The components of GitHub Actions" width="880" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Coordinating complex pipelines
&lt;/h2&gt;

&lt;p&gt;We use CI/CD across most projects at Fastly, both internal and public. I’m going to detail some examples of how we configure this for our &lt;a href="https://developer.fastly.com"&gt;Developer Hub&lt;/a&gt; (DevHub).&lt;/p&gt;

&lt;p&gt;The DevHub is a static website generated using &lt;a href="https://www.gatsbyjs.com"&gt;Gatsby&lt;/a&gt;, and it provides Fastly's developer community with information on, amongst other things&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fastly &lt;a href="https://developer.fastly.com/reference/api/"&gt;public APIs&lt;/a&gt;, and language references for &lt;a href="https://developer.fastly.com/learning/vcl/"&gt;VCL&lt;/a&gt; and &lt;a href="https://developer.fastly.com/learning/compute/"&gt;Compute@Edge&lt;/a&gt; services.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.fastly.com/learning/tools/"&gt;Fastly tools&lt;/a&gt; that simplify the setup, testing, validation and deployment of your code.&lt;/li&gt;
&lt;li&gt;A plethora of &lt;a href="https://developer.fastly.com/solutions/"&gt;solutions&lt;/a&gt;, &lt;a href="https://developer.fastly.com/solutions/examples"&gt;examples&lt;/a&gt; and &lt;a href="https://developer.fastly.com/learning/concepts/"&gt;architectural concept information&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’re going to dig into DevHub's multi-layered CI/CD process and see how we have utilised the various features of the GitHub Actions platform to support some of its more complex requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10,000-foot view
&lt;/h2&gt;

&lt;p&gt;Let’s start with a visualisation of what we’re dealing with when it comes to DevHub’s CI/CD pipeline. Below is a “summary view” of our DevHub pipeline that the GitHub Actions UI provides. It is an interactive graph that lets us interact with each of the available jobs and to view their individual progress and status.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ol7GpmBo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9rtkj2ci1cr0argi1aw7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ol7GpmBo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9rtkj2ci1cr0argi1aw7.png" alt="Visualisation of DevHub pipeline" width="880" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we can see we start with two consecutive jobs, i.e. one must complete before the other&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gatsby Build&lt;/li&gt;
&lt;li&gt;Deploy to GCS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are consecutive because we have to build our application before we can deploy it. &lt;/p&gt;

&lt;p&gt;This pipeline is the same for both our development and production environments. The only difference is the subdomain to which the Gatsby application is deployed.&lt;/p&gt;

&lt;p&gt;When a &lt;a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests"&gt;PR&lt;/a&gt; is opened on our DevHub GitHub repository, the code is deployed to a dynamically generated subdomain for &lt;a href="https://en.wikipedia.org/wiki/Quality_assurance"&gt;QA&lt;/a&gt;. When the PR is merged the deployment is to our production endpoint &lt;a href="https://developer.fastly.com"&gt;developer.fastly.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After the first two consecutive jobs, we have multiple jobs that are run in parallel, handling things like checking for stale content that needs to be reviewed, updating search metadata, and purging the Fastly CDN to ensure users see the latest content.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HINT&lt;/strong&gt;: Jobs are by default run in parallel. If a job has a dependency on another job, this can be made explicit by using &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds"&gt;&lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.needs&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We then introduce a blocking job called “Post-deployment checks” that validates whether any updated files are related to our Compute@Edge services, or if any of the code examples for the various Compute@Edge &lt;a href="https://en.wikipedia.org/wiki/Software_development_kit"&gt;SDK&lt;/a&gt; languages have been updated. If they have, the final set of jobs will be executed, which deploys the updated service code, and validates our code examples are correct.&lt;/p&gt;

&lt;p&gt;Some of the jobs don’t need to run every time. For example, there are jobs defined that only run on a predetermined schedule and others that only need to run when we're building the production environment (i.e. are skipped as part of the PR review process). We’ll see how this is handled later on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storing your workflows
&lt;/h2&gt;

&lt;p&gt;Defining pipelines with GitHub Actions requires defining jobs within a workflow file. You can have any number of workflow files, which are stored in a .github/workflows folder within the root directory of your project repository.&lt;/p&gt;

&lt;p&gt;A tree view of this directory structure might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── .github
│   └── workflows
│       ├── my-first-workflow.yaml
│       ├── my-second-workflow.yaml
│       └── my-third-workflow.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling events
&lt;/h2&gt;

&lt;p&gt;The jobs you define within a workflow file are triggered based on specific events. GitHub Actions offer a &lt;a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#available-events"&gt;rich set of events&lt;/a&gt; that let you fine-tune your workflow using the &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on"&gt;&lt;code&gt;on&lt;/code&gt;&lt;/a&gt; key for numerous use cases. Some common examples of triggering a workflow are:&lt;/p&gt;

&lt;h3&gt;
  
  
  When pushing changes to any branch
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;push&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When pushing changes to specific branches
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-feature-branch"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-other-feature-branch"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When pushing a tag
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scheduling execution at a specific time or interval
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;   &lt;span class="c1"&gt;# https://crontab.guru/every-month&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*/5&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt; &lt;span class="c1"&gt;# https://crontab.guru/every-5-minutes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HINT&lt;/strong&gt;: &lt;a href="https://crontab.guru/"&gt;https://crontab.guru/&lt;/a&gt; makes dealing with the &lt;a href="https://en.wikipedia.org/wiki/Cron"&gt;cron&lt;/a&gt; syntax easy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A 'Hello World' job
&lt;/h2&gt;

&lt;p&gt;Before we jump into more detailed examples, let’s look at a simplified workflow file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My Workflow&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;push&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;a-simple-job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Say Hello&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo 'hello'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt; 
          &lt;span class="s"&gt;echo 'foo'&lt;/span&gt;
          &lt;span class="s"&gt;echo 'bar'&lt;/span&gt;
          &lt;span class="s"&gt;echo 'baz'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ve already seen the &lt;code&gt;on&lt;/code&gt; key, but now we have the basic building blocks that you’ll see in nearly all types of workflows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#name"&gt;&lt;code&gt;name&lt;/code&gt;&lt;/a&gt; (optional): the name of your workflow file.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobs"&gt;&lt;code&gt;jobs&lt;/code&gt;&lt;/a&gt;: a collection of jobs we want to have executed.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_id"&gt;&lt;code&gt;jobs.&amp;lt;job_id&amp;gt;&lt;/code&gt;&lt;/a&gt;: a unique job identifier (&lt;code&gt;a-simple-job&lt;/code&gt; in our example).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idruns-on"&gt;&lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.runs-on&lt;/code&gt;&lt;/a&gt;: indicates which &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#choosing-github-hosted-runners"&gt;virtual machine&lt;/a&gt; to run our job on.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idsteps"&gt;&lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.steps&lt;/code&gt;&lt;/a&gt;: a collection of steps we want to have executed.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsname"&gt;&lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.steps[*].name&lt;/code&gt;&lt;/a&gt; (optional): the name of your step.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun"&gt;&lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.steps[*].run&lt;/code&gt;&lt;/a&gt;: a command-line program to execute.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each &lt;code&gt;run&lt;/code&gt; key represents a new process and shell. In the above example, we can see two separate steps defined, and the latter step uses a pipe character &lt;code&gt;|&lt;/code&gt; to configure multiline mode. This means each of the &lt;code&gt;echo&lt;/code&gt; commands are run within the same shell instance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: the use of the multiline pipe character requires the following lines to be indented.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The caveat to using multiline mode is that any errors that occur during runtime execution become a lot harder to identify and debug compared to using individual steps. By splitting multiple commands across separate steps, any failure that occurs will be associated with a specific step.&lt;/p&gt;

&lt;p&gt;There are many other &lt;code&gt;steps[*]&lt;/code&gt; keys available, and we’ll see examples of them as we dig more into the DevHub’s example configuration.&lt;/p&gt;

&lt;p&gt;When defining multiple jobs within a workflow we’re able to control the order of the jobs. By defining &lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.needs&lt;/code&gt; and providing it with the relevant &lt;code&gt;jobs.&amp;lt;job_id&amp;gt;&lt;/code&gt; we’re able to inform the GitHub runner that we need a specific job to have been run and completed successfully before the current job can begin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validating against a matrix
&lt;/h2&gt;

&lt;p&gt;For the DevHub we have &lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.runs-on&lt;/code&gt; set to run our workflow on a Ubuntu virtual machine but this particular key is much more powerful when coupled with &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategy"&gt;&lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.strategy&lt;/code&gt;&lt;/a&gt;, which enables you to configure your job to be run on multiple virtual machines. An example of this configuration would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;example_matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;macos-latest&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;windows-latest&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;runs-on&lt;/code&gt; key example above is using an &lt;a href="https://docs.github.com/en/actions/learn-github-actions/expressions"&gt;Expression syntax&lt;/a&gt; we’ve not yet discussed. It’s a feature you’ll use a lot when writing your own workflows. In summary, it lets you dynamically reference other keys and exposed data, and we’ll cover this in more detail in the section on “Accessing contextual data”.&lt;/p&gt;

&lt;p&gt;You can even extend this approach to support a multi-dimensional matrix, enabling you to validate a job not only against multiple platforms but also using different software versions across those platforms. We use this approach in our open-source &lt;a href="https://developer.fastly.com/learning/tools/cli"&gt;Fastly CLI&lt;/a&gt; pipeline when &lt;a href="https://github.com/fastly/cli/blob/69a0f87abf28fc7e319d5b2c3bcabf812413d73a/.github/workflows/pr_test.yml#L54-L60"&gt;validating the CLI test suite&lt;/a&gt;, which needs to produce an executable binary that works across all three major operating systems (Linux, Mac, Windows).&lt;/p&gt;

&lt;p&gt;Refer to the documentation for further &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-using-a-multi-dimension-matrix"&gt;examples of this&lt;/a&gt; type of use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Third-party actions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/actions/creating-actions/about-custom-actions"&gt;Actions&lt;/a&gt; are a collection of steps that can be reused. You can create your own &lt;a href="https://docs.github.com/en/actions/creating-actions/about-custom-actions"&gt;custom actions&lt;/a&gt; but it’s more common to use third-party community actions, for example, Fastly provides &lt;a href="https://github.com/fastly/compute-actions"&gt;actions for building and deploying to Compute@Edge&lt;/a&gt;. To use an action, the workflow job needs a step that includes the &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsuses"&gt;&lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.steps[*].uses&lt;/code&gt;&lt;/a&gt; key set to an appropriate configuration path. &lt;/p&gt;

&lt;p&gt;A common example is using &lt;a href="https://github.com/actions/checkout"&gt;actions/checkout&lt;/a&gt;, which lets a job access the project code inside the repository running the workflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ls&lt;/span&gt; &lt;span class="c1"&gt;# no files/directories available&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ls&lt;/span&gt; &lt;span class="c1"&gt;# lists all root project files/directories&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The DevHub makes heavy use of third-party actions. Here are just a few of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/actions/checkout"&gt;actions/checkout&lt;/a&gt;: access repository files.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/actions/cache"&gt;actions/cache&lt;/a&gt;: cache dependencies and build outputs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/actions/setup-go"&gt;actions/setup-go&lt;/a&gt;: install and configure a specific version of Go.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/actions/setup-node"&gt;actions/setup-node&lt;/a&gt;: install and configure a specific version of Node.js.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/actions-rs/toolchain"&gt;actions-rs/toolchain&lt;/a&gt;: install and configure a specific version of Rust.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;: All of the above actions, with the exception of the last one, are officially maintained by GitHub. Fastly trusts using these actions as they are developed by the same organisation providing the platform. Any non-official third-party actions should be audited first before using as they can access an &lt;a href="https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret"&gt;automatically generated GitHub token&lt;/a&gt; which will enable an action to carry out API requests that otherwise require authentication. See “Managing secrets” for more information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Accessing contextual data
&lt;/h2&gt;

&lt;p&gt;GitHub Actions offers a &lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts"&gt;collection of ‘context’ objects&lt;/a&gt; that provide information for things like your &lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts#env-context"&gt;workflow environment&lt;/a&gt;, &lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts#github-context"&gt;github repository&lt;/a&gt;, &lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts#job-context"&gt;job&lt;/a&gt; and &lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts#steps-context"&gt;step&lt;/a&gt; output, &lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts#secrets-context"&gt;secrets data&lt;/a&gt; and more. &lt;/p&gt;

&lt;p&gt;We use a lot of these context objects in the DevHub workflow to generate cache keys, persist data between jobs, access secrets and other environment variables so we can configure the shell scripts that we run, and identify when a particular flow control should be applied.&lt;/p&gt;

&lt;p&gt;An example of this is when we need to authorise access to private &lt;a href="https://www.npmjs.com"&gt;NPM&lt;/a&gt; packages hosted on GitHub’s package repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" &amp;gt; ~/.npmrc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we use the &lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts#secrets-context"&gt;&lt;code&gt;secrets&lt;/code&gt;&lt;/a&gt; context to access an authentication token and persist it temporarily into a &lt;a href="https://docs.npmjs.com/cli/v8/configuring-npm/npmrc"&gt;~/.npmrc&lt;/a&gt; file used by NPM (we’ll revisit this example later when discussing secrets in more detail).&lt;/p&gt;

&lt;p&gt;Another example is when we use the &lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts#github-context"&gt;&lt;code&gt;github&lt;/code&gt;&lt;/a&gt; context to access the root directory of our checked-out project so we can access a &lt;a href="https://cloud.google.com/docs/authentication/getting-started#setting_the_environment_variable"&gt;Google authentication configuration file&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace}}/.google.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Defining environment variables
&lt;/h2&gt;

&lt;p&gt;These are typically used in CI/CD to inject values at runtime. GitHub Actions lets you configure environment variables in multiple sections within the workflow file, depending on the scope they should have.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Globally&lt;/strong&gt;: available to all jobs (and all steps within those jobs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job level&lt;/strong&gt;: available to all steps within the specific job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step level&lt;/strong&gt;: available for a single step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example that demonstrates all three scope levels, they each use the &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#env"&gt;&lt;code&gt;env&lt;/code&gt;&lt;/a&gt; key to contain the variables being declared:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;GLOBAL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;my-job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;LITERAL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bar&lt;/span&gt;
      &lt;span class="na"&gt;INTERPOLATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.ref_name }}&lt;/span&gt;
      &lt;span class="na"&gt;EXPRESSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(echo ${{ github.ref_name }} | perl -pe 's/[^a-zA-Z0-9]+/-/g')&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Print Global Environment Variables&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo $GLOBAL&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Print Job Environment Variables&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo ${{ env.LITERAL }}&lt;/span&gt;
          &lt;span class="s"&gt;echo ${{ env.INTERPOLATION }}&lt;/span&gt;
          &lt;span class="s"&gt;echo ${{ env.EXPRESSION }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="na"&gt;Print Step Environment Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;STEPVAR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my step&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo ${{ env.STEPVAR }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see at the top of the example we have an &lt;code&gt;env&lt;/code&gt; key where we define an environment variable called &lt;code&gt;GLOBAL&lt;/code&gt;. This environment variable is declared in the top-level global scope and so it’s available at all other levels including job and step levels.&lt;/p&gt;

&lt;p&gt;Next, we can see an &lt;code&gt;env&lt;/code&gt; key defined within the job itself, and within that are three separate environment variables declared &lt;code&gt;LITERAL&lt;/code&gt;, &lt;code&gt;INTERPOLATION&lt;/code&gt; and &lt;code&gt;EXPRESSION&lt;/code&gt;. These variables are available for all the steps within that specific job.&lt;/p&gt;

&lt;p&gt;Lastly, we can see an &lt;code&gt;env&lt;/code&gt; key defined within the final step, and within it is a single environment variable declared &lt;code&gt;STEPVAR&lt;/code&gt;, which is only available to that step and no other.&lt;/p&gt;

&lt;p&gt;You’ll notice we’ve used two different ways to access the environment variables&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$NAME&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;${{ env.NAME }}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first was using a syntax familiar to developers who work within a terminal environment: &lt;code&gt;echo $NAME&lt;/code&gt;. This causes the shell instance (that the &lt;code&gt;echo&lt;/code&gt; command runs inside) to evaluate &lt;code&gt;$NAME&lt;/code&gt; and look up its value from the containing environment. &lt;/p&gt;

&lt;p&gt;The other approach is to use the interpolation feature we looked at earlier: &lt;code&gt;echo ${{ env.NAME }}&lt;/code&gt;, and although the result is the same, the way the values are acquired is subtly different. With interpolation, the value is acquired by looking up the relevant key within the &lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts#env-context"&gt;&lt;code&gt;env&lt;/code&gt;&lt;/a&gt; context object, and then the value is injected into the shell command to be executed (as if the command was literally &lt;code&gt;echo my step&lt;/code&gt;), while the more traditional shell resolution approach works by the shell instance looking up the environment variable to access the value. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HINT&lt;/strong&gt;: I would tend towards using the interpolation approach as it’s more explicit and makes setting environment variables much more ‘visible’ when scanning long workflow files.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the previous example, you might have expected &lt;code&gt;echo ${{ env.EXPRESSION }}&lt;/code&gt; to have printed the result of the expression (i.e. inspect the &lt;code&gt;github.ref_name&lt;/code&gt; context and then use &lt;a href="https://www.perl.org"&gt;Perl&lt;/a&gt; to normalise the value), instead the literal value defined would have been printed.&lt;/p&gt;

&lt;p&gt;This is because the &lt;code&gt;env&lt;/code&gt; key does not evaluate shell commands like &lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.steps[*].run&lt;/code&gt; does, and causes problems when we need an environment variable to contain a dynamically generated value. So how do we do that?&lt;/p&gt;

&lt;p&gt;Well, let’s consider the scenario we had with the DevHub. We were using the third-party action &lt;a href="https://github.com/actions/setup-node"&gt;&lt;code&gt;setup-node&lt;/code&gt;&lt;/a&gt; to install and configure the &lt;a href="https://nodejs.org/"&gt;Node.js&lt;/a&gt; programming language. This action lets you specify the node version to install but it can’t be a dynamically acquired value. You either have to hardcode it or interpolate the value. &lt;/p&gt;

&lt;p&gt;In the DevHub we have a &lt;a href="https://github.com/nvm-sh/nvm"&gt;.nvmrc&lt;/a&gt; file that indicates the supported node version. We want to read the version contained in this file and pass that to the action. There are a few ways to achieve this but the simplest one was to store the value in an environment variable and interpolate the value in the action’s &lt;code&gt;node-version&lt;/code&gt; input field using the &lt;code&gt;env&lt;/code&gt; context object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "NODE_VERSION=$(cat .nvmrc)" &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v2&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;env.NODE_VERSION&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The way this works is we use a shell command to read the node version from the .nvmrc file and assign it to a variable called &lt;code&gt;NODE_VERSION&lt;/code&gt; and finally append that generated string to a file that the GitHub Actions runner uses to generate the environment variables. The file path is provided via the &lt;a href="https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables"&gt;default environment variable&lt;/a&gt; &lt;code&gt;GITHUB_ENV&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing secrets
&lt;/h2&gt;

&lt;p&gt;A GitHub repository can be &lt;a href="https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository"&gt;configured to store secrets&lt;/a&gt;. These secrets are exposed to your GitHub Actions workflow via the &lt;code&gt;secrets&lt;/code&gt; context object. We saw an example of this earlier when we referenced &lt;code&gt;secrets.GITHUB_TOKEN&lt;/code&gt; as a way to access our private NPM packages hosted on GitHub’s NPM package repository.&lt;/p&gt;

&lt;p&gt;That particular secret is a special case - it's always available, since it's provided by the GitHub Actions runner. To quote GitHub directly…&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At the start of each workflow run, GitHub automatically creates a unique GITHUB_TOKEN secret to use in your workflow. When you enable GitHub Actions, GitHub installs a GitHub App on your repository. The GITHUB_TOKEN secret is a GitHub App installation access token. You can use the installation access token to authenticate on behalf of the GitHub App installed on your repository. The token’s permissions are limited to the repository that contains your workflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That last sentence is the important bit. It means yes you can use &lt;code&gt;secrets.GITHUB_TOKEN&lt;/code&gt; to make GitHub API calls on behalf of your repository but you can’t use it to access any other private repository in your organisation or to access GitHub’s package repository. For that we need a PAT (&lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token"&gt;Personal Access Token&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This was a real problem we stumbled into and so for the DevHub it meant we had to create a PAT with the appropriate access permissions, and add it as a repository secret that we could reference via the &lt;code&gt;secrets&lt;/code&gt; context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flow control
&lt;/h2&gt;

&lt;p&gt;GitHub Actions provides a mechanism for &lt;a href="https://docs.github.com/en/actions/using-jobs/using-conditions-to-control-job-execution"&gt;preventing a job from running&lt;/a&gt; unless a specific condition is met. To do this you must assign an expression to &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idif"&gt;&lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.if&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;example-job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.ref_name == "main" }}&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo hello&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above example looks up &lt;code&gt;github.ref_name&lt;/code&gt; and only runs the job if the branch being run is &lt;code&gt;main&lt;/code&gt;. The DevHub uses conditional execution for multiple use cases, such as&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only run &lt;code&gt;gatsby build&lt;/code&gt; (an expensive operation) if we have a cache miss.&lt;/li&gt;
&lt;li&gt;Only recompile the DevHub Compute@Edge service if it has been updated.&lt;/li&gt;
&lt;li&gt;Only run Compute@Edge language tests if relevant code examples were updated.&lt;/li&gt;
&lt;li&gt;Only publish API updates to &lt;a href="https://www.postman.com"&gt;Postman&lt;/a&gt; if it’s a scheduled production event.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HINT&lt;/strong&gt;: The &lt;code&gt;if&lt;/code&gt; expression can omit the outer &lt;code&gt;${{ }}&lt;/code&gt; but I tend to include them because I prefer the visual explicitness.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A more complex conditional used in the DevHub is when the build artifacts are deployed. We check if there were any changes to specific files, and if there were changes, we execute a job to validate those files. We do this because there’s no point in running scripts to validate files that haven’t been changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ contains(needs.post-deployment-checks.outputs.data, 'RUST_CODE_SAMPLE_CHECKS=true') }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This examples uses the &lt;a href="https://docs.github.com/en/actions/learn-github-actions/expressions#contains"&gt;&lt;code&gt;contains&lt;/code&gt;&lt;/a&gt; function to inspect data exposed by another job (&lt;code&gt;post-deployment-checks&lt;/code&gt;). We’ll learn about how the persisting of data between jobs can be achieved in the next section.&lt;/p&gt;

&lt;p&gt;To learn more about the available operators, like &lt;code&gt;==&lt;/code&gt;, &lt;code&gt;!=&lt;/code&gt; and &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, refer to the &lt;a href="https://docs.github.com/en/actions/learn-github-actions/expressions#operators"&gt;operators documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persisting data
&lt;/h2&gt;

&lt;p&gt;Each workflow job is executed within its own runner. This means any files or data that are created will be lost once the job finishes. There are a few ways to persist data:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt;: &lt;a href="https://github.com/actions/cache"&gt;actions/cache&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artifacts&lt;/strong&gt;: &lt;a href="https://github.com/actions/upload-artifact"&gt;actions/upload-artifact&lt;/a&gt;, &lt;a href="https://github.com/actions/download-artifact"&gt;actions/download-artifact&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outputs&lt;/strong&gt;: &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idoutputs"&gt;&lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.outputs&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Caching
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows"&gt;Caching&lt;/a&gt; is the simplest, and most common, way to persist data but it can result in confusing failure scenarios. This has been experienced with the DevHub pipeline on a few occasions. Typically a subtle workflow configuration change will cause the &lt;code&gt;cache&lt;/code&gt; action to look up the wrong key and subsequently we either get back stale data or in some cases no data, both can cause confusing errors in the pipeline.&lt;/p&gt;

&lt;p&gt;Caching requires two stages of configuration. You first need to define a step that indicates what to cache and what the cache key should be, then you need a separate step in another job to extract the persisted files from the cache.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# cache our content&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v2&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path/to/be/cached&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-my-cache-key&lt;/span&gt;

&lt;span class="c1"&gt;# restore from cache (in a separate job)&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v2&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path/to/be/cached&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-my-cache-key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HINT&lt;/strong&gt;: You can specify &lt;a href="https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#input-parameters-for-the-cache-action"&gt;multiple paths&lt;/a&gt; to be cached/restored using the multiline character &lt;code&gt;|&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Whenever the action step is executed it will attempt to lookup the given key in the cache and if cached content is found the content is restored to the given path(s), otherwise it does nothing. When the job completes there is a ‘post run’ hook that each installed action can execute and for the &lt;code&gt;cache&lt;/code&gt; action, when the hook is triggered, it will look at the given path and store whatever it finds there into the cache.&lt;/p&gt;

&lt;p&gt;The DevHub uses the &lt;code&gt;cache&lt;/code&gt; action for caching our node_modules directory (installing node modules is a very slow operation and so the less we have to do that the better), caching build configuration and resulting artifacts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Artifacts
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts"&gt;Artifacts&lt;/a&gt; are much slower at storing files than caching because the files need to be uploaded and downloaded from GitHub’s servers. Unlike caching you can only download a file that was uploaded as part of the same workflow run, but like caching they have two distinct steps that need to be defined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# upload our files&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v3&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-artifact&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_file.txt&lt;/span&gt;

&lt;span class="c1"&gt;# download our files (in a separate job)&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v3&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-artifact&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub’s own recommendation for which approach to take (caching or artifacts) is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use caching when you want to reuse files that don't change often between jobs or workflow runs, such as build dependencies from a package management system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use artifacts when you want to save files produced by a job to view after a workflow run has ended, such as built binaries or build logs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Outputs
&lt;/h3&gt;

&lt;p&gt;Jobs have an &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idoutputs"&gt;&lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.outputs&lt;/code&gt;&lt;/a&gt; key which can produce data for another job to consume. This approach is used heavily in the DevHub workflow to better support caching of expensive resources such as installing node dependencies or running a Gatsby build. &lt;/p&gt;

&lt;p&gt;The DevHub workflow dynamically generates cache keys for resources that we would like subsequent jobs to use, and it uses a job’s output key to persist those cache keys to the next job so that it can look up the cached content using the same keys.&lt;/p&gt;

&lt;p&gt;An example of this is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# job 1&lt;/span&gt;
&lt;span class="na"&gt;example-job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cache_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.example-build.outputs.cache_key }}&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run a build&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make build&lt;/span&gt; &lt;span class="c1"&gt;# generates a ./build directory&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example-build&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "::set-output name=cache_key::${{ hashFiles('./build') }}"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache the build&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./build&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-${{ needs.example-job.outputs.cache_key }}&lt;/span&gt;

&lt;span class="c1"&gt;# job 2&lt;/span&gt;
&lt;span class="na"&gt;other-job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example-job&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Extract the build from the cache&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./build&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-${{ needs.example-job.outputs.cache_key }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the first job (&lt;code&gt;example-job&lt;/code&gt;) we see the &lt;code&gt;outputs&lt;/code&gt; key is defined with a nested &lt;code&gt;cache-key&lt;/code&gt; set to evaluate the expression &lt;code&gt;steps.example-build.outputs.cache_key&lt;/code&gt;. This expression results in assigning the output from the step with &lt;code&gt;id: example-build&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;outputs.cache_key&lt;/code&gt; in the expression is a reference to the output from the &lt;code&gt;example-build&lt;/code&gt; step. The step’s &lt;code&gt;run&lt;/code&gt; key executes the &lt;code&gt;echo&lt;/code&gt; command and produces output that is structured in a specific format that GitHub Actions recognises and allows the command to communicate with the runner machine. &lt;/p&gt;

&lt;p&gt;In this case, the command outputs a string containing the &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter"&gt;&lt;code&gt;::set-output&lt;/code&gt;&lt;/a&gt; workflow command followed by &lt;code&gt;name=cache_key&lt;/code&gt; and assigns the named key a value which is the result of hashing the &lt;code&gt;./build&lt;/code&gt; directory (generated in the previous step &lt;code&gt;name: Run a build&lt;/code&gt;) using the &lt;a href="https://docs.github.com/en/actions/learn-github-actions/expressions#hashfiles"&gt;&lt;code&gt;hashFiles&lt;/code&gt;&lt;/a&gt; function. &lt;/p&gt;

&lt;p&gt;The digest that is generated is assigned to the &lt;code&gt;example-build&lt;/code&gt; step output and is exposed to other steps (and the job) using the name &lt;code&gt;cache_key&lt;/code&gt;. The job itself ensures the &lt;code&gt;cache_key&lt;/code&gt; value from the step is assigned to its own &lt;code&gt;outputs.cache_key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The second job (&lt;code&gt;other-job&lt;/code&gt;) states that it &lt;code&gt;needs: example-job&lt;/code&gt;. Once that dependency is declared, &lt;code&gt;other-job&lt;/code&gt; can access the output from &lt;code&gt;example-job&lt;/code&gt; using the &lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts#needs-context"&gt;&lt;code&gt;needs&lt;/code&gt;&lt;/a&gt; context object referenced using the expression syntax &lt;code&gt;${{ … }}&lt;/code&gt;. In this case, &lt;code&gt;other-job&lt;/code&gt; acquires the persisted cache key with &lt;code&gt;needs.example-job.outputs.cache_key&lt;/code&gt; and passes the digest value to the &lt;code&gt;actions/cache&lt;/code&gt; action so it may lookup the &lt;code&gt;./build&lt;/code&gt; directory and restore it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reusable workflows
&lt;/h2&gt;

&lt;p&gt;The DevHub pipeline has a bunch of ‘post-deployment’ jobs that validate the content produced by the Gatsby build system. For example we run scripts that check for broken links, and scripts that run test suites against the different &lt;a href="https://developer.fastly.com/learning/compute/"&gt;Compute@Edge&lt;/a&gt; programming language &lt;a href="https://developer.fastly.com/solutions/examples/"&gt;examples&lt;/a&gt; provided on the DevHub site.&lt;/p&gt;

&lt;p&gt;Each validation process is defined as a separate job. The steps for these validation jobs are typically the same but the script that is executed will be unique to the specific job, and this means all the steps that are required to enable the validation process have to be duplicated across each validation job. To help reduce the duplication of steps we define a set of &lt;a href="https://docs.github.com/en/actions/using-workflows/reusing-workflows"&gt;reusable workflows&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A reusable workflow is a yaml file that has a structure very similar to the standard workflow yaml file we’ve seen so far. For example, you define a &lt;code&gt;jobs&lt;/code&gt; key which determines what platform the job should run on, any environment variables that need to exist, and a set of steps that should be executed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Reusable workflows don’t inherit the parent workflow environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The only difference is that a reusable workflow file is defined as being a ‘template’ and it has access to an extra ‘event’ called a &lt;code&gt;workflow_call&lt;/code&gt;. Within the &lt;a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_call"&gt;&lt;code&gt;workflow_call&lt;/code&gt;&lt;/a&gt; key you can define a collection of &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_callinputs"&gt;&lt;code&gt;inputs&lt;/code&gt;&lt;/a&gt; and (optionally) &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_callsecrets"&gt;&lt;code&gt;secrets&lt;/code&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;workflow_call&lt;/code&gt; event is triggered by the main workflow file when it defines a job that references the reusable workflow via the &lt;code&gt;uses&lt;/code&gt; key. The main workflow file is able to pass its own values for the defined &lt;code&gt;inputs&lt;/code&gt; and &lt;code&gt;secrets&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;An example reusable workflow might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;inputs&lt;/code&gt; and &lt;code&gt;secrets&lt;/code&gt; can be referenced within the reusable workflow’s steps using the expression syntax &lt;code&gt;${{ inputs.script }}&lt;/code&gt; and &lt;code&gt;${{ secrets.api_key }}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The skeleton outline of the DevHub’s main workflow, with a call to the reusable workflow, looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;

  &lt;span class="na"&gt;validate-foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;org&amp;gt;/&amp;lt;repo&amp;gt;/.github/workflows/&amp;lt;filename&amp;gt;@&amp;lt;branch&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./foo.sh&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secret_squirrel&lt;/span&gt;

  &lt;span class="na"&gt;validate-bar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;org&amp;gt;/&amp;lt;repo&amp;gt;/.github/workflows/&amp;lt;filename&amp;gt;@&amp;lt;branch&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./bar.sh&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secret_squirrel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reusable workflows help reduce boilerplate by abstracting away common steps. Without this feature the DevHub post-deployment validation jobs would easily have caused the overall workflow file to quadruple in size and complexity, and also would have resulted in a maintenance nightmare if ever we needed to change a set of steps, as we otherwise would have had to remember to change them in multiple places instead of just one reusable workflow file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;GitHub Actions is a CI platform that offers a very flexible and expressive configuration model, supporting a wide-ranging eco-system of community-driven third-party tools, all for free and directly integrated with where your code likely already exists, on the largest code repository platform available today. Try it out for yourself and see how you might simplify your existing CI workflows and take advantage of its rich feature set.&lt;/p&gt;

</description>
      <category>github</category>
      <category>ci</category>
      <category>cdn</category>
      <category>gatsby</category>
    </item>
  </channel>
</rss>
