From 2698c99d6617141fb7f25a0a2b0eaf2985331d35 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Mon, 30 Oct 2023 16:57:04 -0700 Subject: [PATCH] feat: adds guide for the Go programming language (#66) --- docs/mkdocs.yml | 4 + docs/src/guides/index.md | 11 ++ docs/src/guides/languages/go.md | 225 ++++++++++++++++++++++++++++++++ docs/src/index.md | 1 + docs/src/onboarding/index.md | 17 ++- examples/go/Earthfile | 2 + 6 files changed, 254 insertions(+), 6 deletions(-) create mode 100644 docs/src/guides/index.md create mode 100644 docs/src/guides/languages/go.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 677044792..3f0e63c23 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -9,6 +9,10 @@ nav: - Introduction: index.md - Onboarding: onboarding/index.md - Style Guide: style.md + - Guides: + - guides/index.md + - Languages: + - Go: guides/languages/go.md - Reference: - reference/index.md - Workflows: reference/workflows.md diff --git a/docs/src/guides/index.md b/docs/src/guides/index.md new file mode 100644 index 000000000..4af5ca414 --- /dev/null +++ b/docs/src/guides/index.md @@ -0,0 +1,11 @@ +# Guides + +This section contains guides for using the Catalyst CI to perform common tasks. +If you're just getting started with Catalyst CI, these guides will prove useful for getting going as fast as possible. +Each guide contains a full reference `Earthfile` as well as step-by-step instructions for building it. + +Most guides are broken down by a particular task. +The language section contains guides for getting started with a particular programming language. +If you don't see a guide, please feel free to [submit an issue](https://github.com/input-output-hk/catalyst-ci/issues) requesting +it. +Even better, open a PR that adds the guide! diff --git a/docs/src/guides/languages/go.md b/docs/src/guides/languages/go.md new file mode 100644 index 000000000..229d6c1cb --- /dev/null +++ b/docs/src/guides/languages/go.md @@ -0,0 +1,225 @@ +# Go + +## Introduction + + +!!! Tip + If you're just looking for a complete example, + [click here](https://github.com/input-output-hk/catalyst-ci/blob/master/examples/go/Earthfile). + This guide will provide detailed instructions for how the example was built. + + +This guide will get you started with using the Catalyst CI to build Go-based projects. +By the end of the guide, we'll have an `Earthfile` that utilizes the Catalyst CI to build, release, and publish our Go program. + +To begin, clone the Catalyst CI repository: + +```bash +git clone https://github.com/input-output-hk/catalyst-ci.git +``` + +Navigate to `examples/go` to find a very basic Go program which prints "Hello, world!" to the terminal screen using `cowsay`. +This folder already has an `Earthfile` in it. +This is the `Earthfile` we will be building in this guide. +You can choose to either delete the file and start from scratch, or read the guide and follow along in the file. + +## Building the Earthfile + + +!!! Note + The below sections will walk through building our `Earthfile` step-by-step. + In each section, only the fragments of the `Earthfile` relative to that section are displayed. + This means that, as you go through each section, you should be cumulatively building the `Earthfile`. + If you get stuck at any point, you can always take a look at the + [example](https://github.com/input-output-hk/catalyst-ci/blob/master/examples/go/Earthfile). + + +### Installing dependencies + +```Earthfile +VERSION 0.7 + +deps: + # This target is used to install external Go dependencies. + FROM golang:1.20-alpine3.18 + WORKDIR /work + + # Any build dependencies should also be captured in this target. + RUN apk add --no-cache gcc musl-dev + + # This UDC automatically copies the go.mod and go.sum files and runs + # `go mod download` to install the dependencies. + DO ../../earthly/go+DEPS --ginkgo="false" +``` + +The first target we are going to create will be responsible for downloading the external dependencies that our Go program uses. +By design, the only files we need for this are the `go.mod` and `go.sum` files. +By only copying these files, we ensure that this target is cached for most development and only rebuilds when we add new +dependencies. + +We are going to be inheriting from an alpine image because we are pushing for a fully static build. +When using example as a starting place, feel free to change the base image. +This image is *only* used during the build steps, so it's not important we minimize its size. +Keep in mind, though, excessively large targets can impact the speed of the caching step (due to lots of I/O). + +This target is also going to build responsible for installing external build dependencies. +These are dependencies that are not specific to a language and usually get installed system-wide. +In our case, since we're building a static binary, we need `gcc` and `musl`. + +Finally, the actual logic we will be using is encapsulate in a UDC. +This is a very common pattern, as an `Earthfile` can get repetitive across a repository. +In our case, we use the `go+DEPS` UDC that will automatically copy our `go.mod` and `go.sum` files and then execute +`go mod download`. +The UDC will also establish a cache for the Go tooling. +This means that, even if our source code changes, we'll see a substantial speed boost when compiling because the cache is preserved +across Earthly runs. + +### Running checks + +```Earthfile +src: + # This target copies the source code into the current build context + FROM +deps + + COPY --dir cmd . + +check: + # This target checks the overall health of the source code. + FROM +src + + # This UDC validates the code is formatted according to Go standards. + DO ../../earthly/go+FMT --src="go.mod go.sum cmd" + + # This UDC runs golangci-lint to check for common errors. + DO ../../earthly/go+LINT --src="go.mod go.sum cmd" +``` + +With our dependencies installed, we're now ready to start operating with the source code. +To do this, we establish a dedicated `src` target that is solely responsible for copying the local source code into the context. +This is a common pattern as it ensures we perform this only once and it makes future changes easier (as we only copy in a single +place). +Any future targets which need access to the source code will inherit from this target. + +Now that the source code is available, we can begin performing static checks. +These checks are intended to verify the code is healthy and conforms to a certain standard. +As we did in the previous section, here we rely on UDCs again to perform these checks. +These two UDCs will validate the code formatting is correct and also perform a series of lints to validate code quality. + +Note that these checks are fast (compared to later steps) and perform quick feedback on code quality. +Since this is the first target run in CI, we want to fail the CI as quickly as possible if we can easily find code quality issues. +In future targets, we will run tests which can tend to take significantly more time to run than static analysis tools. + +### Build and test + +```Earthfile +build: + # This target builds the application. + FROM +src + + # The below just creates a fully static binary with no CGO dependencies. + ENV CGO_ENABLED=0 + RUN go build -ldflags="-extldflags=-static" -o bin/hello cmd/main.go + + # We save the artifact here so that future targets can use it. + SAVE ARTIFACT bin/hello hello + +test: + # This target runs unit tests. + FROM +build + + RUN go test ./... +``` + +With the basic checks out of the way, it's finally time to compile our Go program. +Since we need the source code to do this, we'll inherit from the `src` target. +The actual build process is fairly straight-forward, and the additional flags are simply there to ensure a fully static output is +created. + +It's important we use `SAVE ARTIFACT` at the end of the build to make the compiled binary available to other targets. +Remember that targets can reference other targets, even ones in another `Earthfile`. +So by `SAVE ARTIFACT` here, we're making this binary available to any other `Earthfile` which may need to use it. + +Finally, after building the binary, we will run our tests. +In the case of this example, there are no actual tests to run, so the target will complete very quickly. +However, we would expect a more complex project to contain many tests. + +Notice that we inherit from the `build` target: this is because in most cases there will be a layer of compilation before actually +running tests. +By choosing to make our `test` target inherit from `build`, we ensure that we can maximize reusing the cached binary that was +created in the previous target. +This is a good pattern to observe and follow where practical, as it can dramatically improve CI times. + +### Releasing + +```Earthfile +release: + # This target is called by the CI when performing a release. It should use + # `SAVE ARTIFACT` to save the release artifact which is then picked up by + # the CI. + FROM +build + + SAVE ARTIFACT bin/hello hello +``` + +With our source code checked, binary built, and tests passed, we're now ready to release. +The release target will instruct the CI to take whatever artifact we save in this target and automatically include it as part of a +GitHub Release when a new release is created (i.e., a new git tag is created). +In some cases, it makes sense to skip this step, like for projects which produce artifacts that are only usable in a container. +For our case, since our example program prints something to the terminal (and it's statically built), it makes sense to release the +binary by itself. + +The actual logic used in this target is minimal. +Since we already built the binary in the `build` target, we can simply inherit from it and do another `SAVE ARTIFACT`. +This may seem redundant, but the CI sees the `build` and `release` targets as two separate steps. + +Note that we could have also sourced from a new image and used `COPY +build/hello .` with `SAVE ARTIFACT`. +However, if we did this, we would be needlessly adding time to the CI by creating a new image with a single layer. +It's much faster to simply inherit from the target and then use `SAVE ARTIFACT`. + +### Publishing + +```Earthfile +publish-example: + # This target is called by CI when publishing images. It should use the + # `SAVE IMAGE` command to save the image which is then picked up by the CI. + # Note that we start from a "fresh" base image. + FROM alpine:3.18 + WORKDIR /app + ARG tag=latest # Prefer to use `latest` by default, the CI will override this. + + COPY +build/hello . # Use the cached artifact from the build target. + + ENTRYPOINT ["/app/hello"] + SAVE IMAGE hello:${tag} +``` + +Now we want to publish a container image that runs our binary. +While the actual use-case for a container is a bit vague, we create one here for demonstration purposes. +It also serves as a natural way to use our program without having to rely on a package manager (i.e. `docker run ...`). + +In this target, it's important we start with a "fresh" image by using `FROM`. +Unlike the previous targets, here the size of the image matters as the resulting image will be published to a registry. +In our case, we just take a plain `alpine` image as we don't need any of the Go tooling we used previously (since the binary has +already been built for us). + +We add a `tag` argument out of convention. +During local testing, it's sometimes helpful to call `earthly +publish` and specify a different tag to test different versions of +the container image. +However, the CI will *not* pass this argument when it executes the target, so it's important to always set a default value. +Instead, the CI will re-tag the image with the appropriate tags after building it. + +Since the binary has already been built, we can simply `COPY` it from the `build` target. +As in the `release` target, this ensures we use the cache as much as possible. +It also meets the best practice of having a single place where artifacts are built and copied from. + +Finally, we set the appropriate entrypoint and use `SAVE IMAGE` to instruct Earthly to save the final container image. +When the CI executes this target, it will automatically detect the saved image and publish it to the configured container +registries. + +## Conclusion + +You can see the final `Earthfile` [here](https://github.com/input-output-hk/catalyst-ci/blob/master/examples/go/Earthfile). +This `Earthfile` will check the health of our source code, build and test our binary, and then finally release it to GitHub and +publish it to one or more container registries. +At this point, please feel free to experiment more and run each target individually. +Once you're ready, you can copy this example and modify it for your specific context. diff --git a/docs/src/index.md b/docs/src/index.md index 022f83379..4e2bca2b4 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -8,3 +8,4 @@ It's critical to understand what those tools are and how they interact together The quickest way to get started learning is by going through the [onboarding process](./onboarding/index.md). For more in-depth documentation, please review the [reference section](./reference/index.md). Before contributing to an `Earthfile`, please review the [style guide](./style.md). +If you want to learn how to build an `Earthfile` from scratch, check out the [available guides](./guides/index.md). diff --git a/docs/src/onboarding/index.md b/docs/src/onboarding/index.md index ce742a189..d23a1367a 100644 --- a/docs/src/onboarding/index.md +++ b/docs/src/onboarding/index.md @@ -144,18 +144,23 @@ Due to the relative flexibility of Earthly, it's possible to structure an `Earth To provide consistency across our projects, we have implemented a style guide which brings a level of standardization to `Earthfile`s. -The next step after reviewing the style guide is taking a look at the examples provided in the `catalyst-ci` repository. -The examples are broken up by language and serve as a starter template for getting off the ground quickly. -Reviewing the examples also helps with connecting the concepts discussed in this onboarding guide with actual code. +The next step after reviewing the style guide is taking a look at available [guides](../guides/index.md) for performing specific +tasks with Catalyst CI. +The guides are broken up by language or topic and provide a starter template for getting off the ground quickly. +Reviewing the guides also helps with connecting the concepts discussed in this onboarding guide with actual code. +If the task you're working on is not covered in a guide, it's still recommended you read at least one of the other guides in order +get a more in-depth understanding of building with Catalyst CI. Experimentation is also encouraged. The CI process runs on every single commit, even commits to PRs. You can utilize this fact to begin experimenting with an `Earthfile` and seeing how the CI reacts to different targets. The only limitation is that not all targets execute fully in a PR setting (i.e. no artifacts are published/released). -Finally, if you get stuck, or have a need to understand the the CI process more, there is reference documentation available which -covers not only individual targets, but also how the entire CI process operates under the hood. -You may also use the discussions section of the `catalyst-ci` repository to ask questions specific to the CI process. +Finally, if you get stuck, or have a need to understand the the CI process more, there is +[reference documentation](../reference/index.md) available which covers not only individual targets, but also how the entire CI +process operates under the hood. +You may also use the [discussions](https://github.com/input-output-hk/catalyst-ci/discussions/landing) section of the `catalyst-ci` +repository to ask questions specific to the CI process. You are now equipped and ready to start using the Catalyst CI! We are very open-source friendly and will review all feedback/PRs made against the repository. diff --git a/examples/go/Earthfile b/examples/go/Earthfile index 3a049fab7..0abff001a 100644 --- a/examples/go/Earthfile +++ b/examples/go/Earthfile @@ -1,3 +1,5 @@ +# WARNING: If you modify this file, please update the guide that it is dependent +# on in docs/guides/languages/go.md. VERSION 0.7 # The structure of this Earthfile is derived from the style guide: