Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A new build process, based on GitHub Actions #30283

Merged
merged 6 commits into from
Jan 5, 2022

Conversation

apparentlymart
Copy link
Contributor

@apparentlymart apparentlymart commented Jan 3, 2022

This introduces a new process, based on GitHub Actions, for producing the various release artifacts for Terraform CLI.

The ultimate goal here is for this workflow to build all of the artifacts needed to drive the Terraform CLI release process, so that the actual release process (maintained elsewhere) could consume these artifacts directly and no longer need to access the actual repository content, which in turn means that we can verify all of the artifacts before optionally launching the separate release process. The intended workflow then would be to first have this workflow build on the release branch, then once it's succeeded create a tag from the same commit to produce a releasable build. The branch build will have a synthetic version number including the commit ID, while the tag build would reflect the version number indicated in the tag.

However, for now my goals here are more modest: just to get a new process in place which will give us earlier warning if anything merged to a release branch or included in a tagged version has any build problems. No automated process is actually consuming the results of this for now, and I expect we'll iterate more on this workflow once we're ready to integrate this with a new release process. Which is to say: there are some known and documented limitations here, which we'll hopefully address in later commits, but which are not in scope for this PR.

The effect of merging this will be that any future merges to a release branch or to the main branch will trigger a full build for all of our target platforms, including the construction of the various "wrapper package" types our official process produces (Deb packages, RPM packages, Docker Images). It will then run the end-to-end tests against the linux_amd64, darwin_amd64, and windows_amd64 packages, which should increase our confidence that we built generally-working packages across those three platforms.

Given that, I'd like to merge this to get that relatively-minor benefit for now and also to get some experience with the behavior of this workflow in advance of us integrating it with the rest of the release process. In particular, we'll be able to see how it behaves in our real workflow with release branches and tagging, whereas so far I've only been able to test it on this development branch.


Because for the moment we will not actually be releasing the packages built by this process (we'll continue to release the artifacts produced by the non-public release workflow as part of releasing), this doesn't technically assure us that the released packages would also pass the e2etests once built, but in practice the build process here is similar enough to the one the real release process uses that it should still serve as a good early warning signal.

The later work to integrate this with a new release process that actually consumes all of these artifacts will be in closer collaboration with the release engineering team and I expect it will result in at least some small changes to the details of how this works, so we can make sure this is producing artifacts in a suitable shape for the standard release process. However, for my part I did derive this from the build process templates provided from the release engineering team, and referred to the corresponding internal documentation, and so I believe it should already be broadly compatible with those assumptions and only require minor tweaks later.


Although this PR doesn't modify any "real" Terraform code, it does include some adjustments to the internal/command/e2etest package to make it ready to support the separate build run approach used by this workflow. This separation between building the test harness and running it was an original intention of the e2etest design, to allow us to be sure that we're really testing the actual release executable and not a fresh build from source, but we previously didn't end up actually using it in that mode and so that capability has "bitrotted" a bit over the last few years.

In particular, we introduced a few new tests that expect to be able to build new executables from source during the test run, which is counter to the original idea of a test suite we can run against pre-built executables. As a compromise for now I've just made the test harness skip over those tests when running in this mode, since we'll still get those run on Linux during the normal pull request check process. In a later commit I'd like to adjust this slightly so that the make_archive.sh script also includes the needed supporting executables and thus we'll be able to make the separate test harness run the full test suite as originally intended.


You can preview the effect of this workflow by viewing the Actions runs generated from this workflow on this development branch, though I believe you'll need to be a maintainer in this repository in order to be able to actually download the built artifacts.

Note that there's a quirk in the GitHub Actions UI whereby any artifact you download is always wrapped in a .zip archive container, even if the artifact itself is a .zip file. That's kinda annoying for the various .zip packages we produce here, but is an expected part of how GitHub Actions works and doesn't really hurt for our final intended use because the GitHub Actions steps for consuming these artifacts downstream automatically unpacks the outer wrapping archive to reveal the real .zip file inside. This extra level of wrapping is actually important because GitHub Actions's own packaging cannot preserve file permissions, and so the extra inner .zip file we produced ourselves can preserve the executable permission on the terraform executable.

@apparentlymart apparentlymart requested a review from a team January 3, 2022 20:25
@apparentlymart apparentlymart self-assigned this Jan 3, 2022
@apparentlymart apparentlymart added the 1.1-backport If you add this label to a PR before merging, backport-assistant will open a new PR once merged label Jan 3, 2022
Copy link
Contributor

@alisdair alisdair left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved with one concern about the newly skipped e2e tests

// as it only tries to compile files that were included in the testdata
// (which is true for this test).
t.Skip("can't run without building a new provider executable")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm misunderstanding something, these skipped e2e tests don't seem to run in CI at all any more. They seem to be skipped both in the new build-then-run actions and in the per-commit CircleCI runs.

If that's the case, is it feasible to fix that before merge?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh yes, you are right that the "normal" path which builds a custom binary sneakily updates this variable before it returns:

func setup() func() {
if terraformBin != "" {
// this is pre-set when we're running in a binary produced from
// the make-archive.sh script, since that builds a ready-to-go
// binary into the archive. However, we do need to turn it into
// an absolute path so that we can find it when we change the
// working directory during tests.
var err error
terraformBin, err = filepath.Abs(terraformBin)
if err != nil {
panic(fmt.Sprintf("failed to find absolute path of terraform executable: %s", err))
}
return func() {}
}
tmpFilename := e2e.GoBuild("github.com/hashicorp/terraform", "terraform")
// Make the executable available for use in tests
terraformBin = tmpFilename

I'll fix this up by adding a separate variable to track whether we're using an externally-provided executable or a locally-built one, and then change these conditions to all depend on that instead, as a more robust temporary solution to this quirk. Thanks!

For the moment this is just an experimental additional sidecar package
build process, separate from the one we really use for releases, so that
we can get some experience building in the GitHub Actions environment
before hopefully eventually switching to using the artifacts from this
process as the packages we'll release through the official release
channels.

It will react to any push to one of our release branches or to a release
tag by building official-release-like .zip, .deb, and .rpm packages, along
with Docker images, based on the content of the corresponding commit.

For the moment this doesn't actually produce _shippable_ packages because
in particular it doesn't know how to update our version/version.go file
to hard-code the correct version number. Once Go 1.18 is release and we've
upgraded to it we'll switch to using debug.ReadBuildInfo to determine
our version number at runtime and so no longer need to directly update
a source file for each release, but that functionality isn't yet available
in our current Go 1.17 release.
This uses the decoupled build and run strategy to run the e2etests so that
we can arrange to run the tests against the real release packages produced
elsewhere in this workflow, rather than ones generated just in time by
the test harness.

The modifications to make-archive.sh here make it more consistent with its
originally-intended purpose of producing a harness for testing "real"
release executables. Our earlier compromise of making it include its own
terraform executable came from a desire to use that script as part of
manual cross-platform testing when we weren't yet set up to support
automation of those tests as we're doing here. That does mean, however,
that the terraform-e2etest package content must be combined with content
from a terraform release package in order to produce a valid contest for
running the tests.

We use a single job to cross-compile the test harness for all of the
supported platforms, because that build is relatively fast and so not
worth the overhead of matrix build, but then use a matrix build to
actually run the tests so that we can run them in a worker matching the
target platform.

We currently have access only to amd64 (x64) runners in GitHub Actions
and so for the moment this process is limited only to the subset of our
supported platforms which use that architecture.
This should eventually grow to be a step that actually verifies the
validity of the docs source prior to publishing the artifact that a
downstream publishing pipeline can consume, but for the moment it's really
just a placeholder since we have no such validation step and no downstream
pipeline consuming this artifact.

The general idea here is that the artifacts from this workflow should be
sufficient for all downstream release steps to occur without any direct
access to the Terraform CLI repository, and so this is intended to
eventually meet that ideal but as of this commit the website docs
publishing step _does_ still depend on direct access to this repository.
We can use an extra matrix dimension to select which execution environment
we'll use for each GOOS/GOARCH pair, and thus avoid duplicating the
job definition for darwin just to set runs-on: macos-latest for it.

This is not really an intended use of a matrix dimension because it's
directly related to the existing "goos" one, rather than being an
independent third dimension, but it doesn't matter in practice because
we're using the "include" option to specify exact combinations, and thus
we're not relying on the built-in functionality to generate all possible
matrix combinations.
This workflow only generates artifacts and doesn't need to modify anything
about the repository.
In our build workflow we'll treat Linux distribution packaging (currently
.deb and .rpm packages) as a separate job, instead of embedding it into
the "build" job, so that this step can happen concurrently with the other
derived actions like the docker image build, and the e2etest runs.
# supported platforms. Even within that set, we can only run on
# architectures for which we have GitHub Actions runners available,
# which is currently only amd64 (x64).
# TODO: GitHub Actions does support _self-hosted_ arm and arm64
Copy link

@morganchristiansson morganchristiansson Jan 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also qemu. It is significantly slower as it's cpu instruction emulation. But for high-io/low-cpu tasks it works great.

For building arm64 docker image qemu would work great. It's just RUN apk add and COPY terraform

Just use this github action and then you can exec both amd64 and arm64 binaries: https://github.com/docker/setup-qemu-action.

Running all e2e tests with qemu will likely be slower, but should work ok... Maybe can keep tests amd64 and test arm64 binary? actions/setup-go doesn't allow specifying arch but it's not a problem if amd64 e2etests can tests arm64 binary.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can have a go and updating package-docker to build arm64 docker image and and e2etest-* to test arm64 binary using amd64 test harness if that sounds ok?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @morganchristiansson,

Thanks for the tip!

I believe the packaging helper action we're using here already has some support for packaging across architectures -- I think I saw it work when I was first experimenting. However, I intentionally reduced it to just what our current release pipeline is capable of so that we wouldn't get potentially blocked by irrelevant failures in the meantime, given that we're currently using this only to get early feedback on build/e2etest problems.

I'd like to wait to add new architectures here until our release engineering team is ready to work with us on feeding this into the rest of the new pipeline, so that we can make sure whatever we do here is consistent with how other projects using the same pipeline will do it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw I had a look at https://github.com/hashicorp/actions-docker-build it already uses docker build buildx --platform $PLATFORM but $PLATFORM is only used internally not settable from action parameters. https://github.com/hashicorp/actions-docker-build/blob/v1/action.yml#L222-L224`

If arm64 binary is being built here? It will be easy to add the binary to arm64 docker image like this and will run well...

@github-actions
Copy link
Contributor

I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
1.1-backport If you add this label to a PR before merging, backport-assistant will open a new PR once merged build Auto-pinning enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants