buildkite / agent

The Buildkite Agent is an open-source toolkit written in Go for securely running build jobs on any device or network
https://buildkite.com/
MIT License
810 stars 299 forks source link

Agent should update to the latest Plugin version in some cases #422

Open lox opened 7 years ago

lox commented 7 years ago

Currently the agent will download a plugin for something like blah-blah-plugin from the master branch the first time it needs it, but it won't update it ever again.

This means that go get your changes as you are iterating, you end up updating your pipeline to reference specific commits like blah-blah-plugin#{commit-hash}.

The meta-effect of this is that it's very hard to get your plugin users hotfixes on master. They need to delete the plugin or change their pipeline.

It would be great to fetch the latest version on each job if the plugin isn't a ref or a symbolic ref /cc @sj26.

lox commented 6 years ago

For now the conclusion in discussion with @keithpitt is that we should encourage people to use tags vs branches!

vektah commented 5 years ago

I agree!

For public plugins tags are :ok_hand:, you don't want unexpected changes to external repos breaking builds.

For internal plugins you really want this instant cascade, maybe following master, maybe following a go style branch for breaking changes (v1, v2). In this case it should always pull the latest ref for that branch.

mipearson commented 5 years ago

Assuming that this runs on every step, how much time will checking that the tag/brach is up to date over multiple plugins take?

Github can be .. slow, sometimes.

lox commented 5 years ago

Good question @mipearson! That was why we didn't implement it this way initially.

mipearson commented 5 years ago

... if it's done inside agent (ie, in Go), could be easily done in parallel.

(and I wonder if a similar thing can be done with the s3 secrets plugin which is another slows-down-every-step point)

lox commented 5 years ago

Yeah, most likely. Will experiment.

In terms of the s3 secrets plugin, I just think that s3 is not a good spot for secrets. It's very hard to make efficient. SSM or Secrets Manager is the future there IMO.

Edit: Check out https://github.com/buildkite/aws-secrets-manager-agent-hooks

tigris commented 5 years ago

FWIW you can hack this yourself by doing a git fetch v1 (or whatever "stable" branch you're using, perhaps you can grok the branch from vars) as early as possible in the hook chain. E.g. the environment hook.

The downside is you now can't use the environment hook for anything else.

Plus the obvious existing downsides of using a mutable branch, including but not limited to non-repeatable builds, slower build steps, etc.

But the upside of instant release. Great for things you deem internally to be sufficient for the risks, e.g. vulnerability scanning. If you trust the source code chain of access, then I can see this being quite beneficial.

evandam commented 4 years ago

:+1: for this, not sure if there have been any recent developments.

I'm looking to manage a plugin internally that other teams in my org will use, and I'd love for them to be able to pull the latest version of the plugin without needing to update their pipelines, make sure each feature branch being built references the correct version, etc.

I agree for public-facing plugins that referencing tags/releases is the way to go, but I'd like to have the option to pull the latest when possible rather than let plugins go stale.

Alternatively, is there a recommendation for the meantime that we might be able to accomplish with an agent hook perhaps that goes into $BUILDKITE_PLUGINS_PATH, loops over $BUILDKITE_PLUGINS checking for unpinned versions and does a git pull?

KaanOzkan commented 4 years ago

@lox +1. Is this option on the roadmap? It has been a year since you commented on this.

pda commented 4 years ago

Our current thinking is that:

Any feedback on whether a one hour cache time would be sensible?

sj26 commented 4 years ago

unless they've already been updated in the past hour

This sounds sensible to me.

mipearson commented 4 years ago

This makes sense.

Would it still check for an update if pinned to a tag (or other ref that isn't a commit hash)?

Not a use case we have right now, but it'd be nice to tell users to use #v1 of a plugin, then work on #v2 but still have bugfixes to the #v1 tag/branch come through automatically.

sj26 commented 4 years ago

I think convention is that tags shouldn't change, so I'd expect a tag to continue working as it does.

A common convention to provide version updates is to create a "1-stable" branch or similar which can be tracked.

We've also spoken about semver-style tag negotiation, but that seems much more complicated without proportional benefit, at least for the moment.

mipearson commented 4 years ago

Yep - so, same question as above, but they've specified a branch id - the refresh once-per-hour behaviour would apply?

sj26 commented 4 years ago

That's my understanding! 🙌

@pda?

pda commented 4 years ago

@mipearson @sj26 I think ideally we'd treat tags as immutable (even though git doesn't), but how would we differentiate between a tag and a branch? Unless there's a good answer to that, I'd say the update after an hour would apply to anything that isn't definitely a commit hash.

pda commented 4 years ago
  • the agent should update plugins before every step/job
    • unless they've already been updated in the past hour

Oh, and I left the reasoning for that implicit, I should have been explicit.

Part of the concern is performance — each build step fetching multiple plugins every single time could slow things down substantially.

The other concern is rate limiting — if a plugin is used org-wide in a large organisation, it could be triggered thousands of times per minute. Apart from being inefficient, fetching it each time could incur rate limiting from the plugin repo provider (👋🏼 GitHub).

sj26 commented 4 years ago

... how would we differentiate between a tag and a branch?

[[ "$(git rev-parse --symbolic-full-name "master")" == refs/tags/* ]] && echo yes || echo no
no

[[ "$(git rev-parse --symbolic-full-name "v1.0")" == refs/tags/* ]] && echo yes || echo no
yes
mipearson commented 4 years ago

I'd err on not treating tags as immutable, personally, in large part because git doesn't. You've then got a solid split between "refs that are immutable" (commit hashes) and "refs that might change" (everything else).

sj26 commented 4 years ago

Hmm, I think I disagree.

Git tags are designed to be immutable. You have to try really hard to mutate them:

$ git tag test 
fatal: tag 'test' already exists

$ git tag --force test
Updated tag 'test' (was a862e5c)

$ git push origin test
To https://github.com/sj26/test.git
 ! [rejected]        test -> test (already exists)
error: failed to push some refs to 'https://github.com/sj26/test.git'
hint: Updates were rejected because the tag already exists in the remote.

$ git push --force origin test
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (4/4), 1.04 KiB | 1.04 MiB/s, done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/sj26/test.git
 + a862e5c...8b625d6 test -> test (forced update)

I'd say we should respect that design and treat tag mutations as exceptional, the same way that force pushing a commit makes it inaccessible is considered exceptional.

Tags are a good, conventional way for us to make common use cases like pinning to a version much, much faster, and more reliable. I think this outweighs the risk of a forced tag mutation.

mipearson commented 4 years ago

TIL. I had a mental model in my head that tags were easily mutable which was not correct, as you've shown. :)

pda commented 4 years ago

Sounds good 👌🏼

staticfloat commented 3 years ago

We are working around this issue by putting the following in our agent environment hook; it has a few problems:

# Update all our git repositories, in parallel
for d in /buildkite/plugins/*-plugin*; do
    TAG="${d#*-plugin}"
    TAG="${TAG#-}"

    # If the tag is a git literal, don't bother to update it
    if [[ ${TAG} =~ [0-9a-z]{40} ]]; then
        continue
    fi

    # If we didn't properly determine the tag name, skip
    if ! git -C "${d}" rev-parse "${TAG}" >/dev/null 2>&1; then
        continue
    fi

    # Do git update/checkout in parallel
    (
        echo "Updating ${d} to latest tag ${TAG}";
        git -C "${d}" fetch --tags --force &&
        git -C "${d}" checkout "${TAG}"
    ) &
done

# Wait for all `git` operations to finish
wait

We use a github action to create major-only tags such that when we release vX.Y the vX tag is updated to point to the latest vX.Y tag, so that users can depend on the latest API-compatible release by specifying vX, or locking to a specific release by specifying vX.Y.

tinder-tremaineeto commented 2 years ago

I'd like to +1 this ask -- we're hoping to use the #latest tag in multiple pipelines, but the fact that the agent won't always update the tag once checked out means that the plugin could be out of date with what we have tagged as #latest. Having a way around this would be awesome.