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

Tag docker target images with a unique name #31

Merged
merged 19 commits into from
Oct 18, 2018
Merged

Conversation

CyrusNajmabadi
Copy link
Contributor

We now tag all our docker pushed builds with a tag equal to their image-id. i.e. we'll end up tagging a build like so:

<awsid>.dkr.ecr.<region>.amazonaws.com/image-name:<image-id> or
<awsid>.dkr.ecr.<region>.amazonaws.com/image-name:<user-tag>-<image-id>

By adding teh image-id into the tag name, we then have a unique and stable name for the target build that can be known at preview time. It also means that any time docker produces a new image id, we end up replacing the image. This helps us avoid needing to hack things in at the cloud layer where we place hte image-id in the environment variable map in order to get pulumi to want to replace the cloud.Service resource.

As part of this change docker.buildAndPushImage now retursn that entire target name, instead of just returning the image-id like it used to before.

docker/docker.ts Outdated
// this tag doesn't require actually pushing the image, nor does it require communicating with
// some external system, making it suitable for unique identification, even during preview.
// This also means that if docker produces a new imageId, we'll get a new name here, ensuring that
// resources will be appropriately replaced.
Copy link
Contributor

Choose a reason for hiding this comment

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

What resources?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will document. but htings like cloud.Service.

});

this.id = imageData.apply(d => d.id);
this.digest = imageData.apply(d => d.digest);
this.imageName = imageData.apply(d => d.uniqueTargetName);
Copy link
Contributor

Choose a reason for hiding this comment

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

OK, so the idea here is that consumers can key off of imageName and expect to get replaced or updated if the image changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup!

docker/docker.ts Outdated
cacheFrom = pullCacheAsync(imageName, cacheFromParam, login, repositoryUrl, logResource);
} else {
cacheFrom = Promise.resolve(undefined);
cacheFrom = await pullCacheAsync(imageName, cacheFromParam, login, repositoryUrl, logResource);
Copy link
Contributor

Choose a reason for hiding this comment

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

Did we have to change this? I'm a little nervous that this subtly changes some execution behaviour that we depended on. (We now block on the pullCacheAsync instead of running it in parallel with other work below?). Given that we don't have much test coverage around the cacheFrom support, I'm inclined to not change it unless we have to - and if we do, to ideally add some additional test coverage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do not. I'll revert this. It seemed like this existed at a time when we didn't have async/await here and were instead using Outputs. We cleaned that up at one point, but this stuff still remained. changing this made things clearer for me. However, you're correct this isn't necessary, so i'll revert :)

docker/docker.ts Outdated

if (!repositoryUrl) {
throw new ResourceError("Expected repository URL to be defined during push", logResource);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need this? The parameter is marked non-optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was preserving/moving this from existing code. But you're right that it shouldn't be necessary in either case. will remove.

docker/docker.ts Outdated
// some external system, making it suitable for unique identification, even during preview.
// This also means that if docker produces a new imageId, we'll get a new name here, ensuring that
// resources (like docker.Image and cloud.Service) will be appropriately replaced.
const uniqueTargetName = createTargetName(repositoryUrl, tag, imageId);
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps uniqueImageName or uniqueTaggedImageName?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure.

docker/docker.ts Outdated
}
}

return buildResult.id;
return uniqueTargetName;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is returning this instead of id a breaking change? I think not - that it is still a unique identifier that is sufficient for any use case of the previous return value - but just want to make sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

right. it's only 'breaking' in the sense if somoene took a hard dependency on this exactly being docker's image ID. but we never doc'ed that, so it would be their issue for doing this. It's not an issue for our own docker.Image or cloud.Service consumers.

docker/docker.ts Show resolved Hide resolved
docker/docker.ts Outdated
// A tag name must be valid ASCII and may contain lowercase and uppercase letters, digits,
// underscores, periods and dashes. A tag name may not start with a period or a dash and may
// contain a maximum of 128 characters.
const fullTag = pieces.join("-").replace(/[^-_.a-zA-Z0-9]/, "").substr(0, 128);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have to do this? If the user provided a tag - it's their fault if it isn't legal and I don't think we should try to "fix" it. If the imageID from Docker is not legal to use here - we should probably do a more intentional transformation on it. Or if we are paranoid that it's format may change arbitrarily - at least only do this on the imageId component?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that's a fair point. The major concern is simply the length. If the caller provides a long tag length, and we jam on the hash, and we go past 128, then that's going to blowup. But perhaps we just let that blowup in that case....

// That's fine, just move onto the next stage.
const { code } = await runCommandThatCanFail(
"docker", ["pull", image], logResource,
/*reportFullCommand:*/ true, /*reportErrorAsWarning:*/ true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this fixing some latent bug in the existing code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will doc. The idea here is that 'pulling' during the cacheefrom code will fail the command the first time (when things aren't cached locally). This ends up with a pulumi update that succeeds, but which prints an error at the end. This simply changes things to not print out that command failure as an 'error' but instead as a 'warning'.

// Trim off the hash kind if there is one.
let imageId = inspectResult.trim();
const colonIndex = imageId.lastIndexOf(":");
imageId = colonIndex < 0 ? imageId : imageId.substr(colonIndex + 1);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we trim this off? Shouldn't we return the full imageID just so that we expose exactly what Docker uses as the image ID?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ImageID is "algorithm:hex". I trim off the algorithm: part since we can't have colons in the tag portion. As we want just a unique tag, the hex portion is sufficient. I could include the algorithm. but it seems really superfluous...

Thoughts?

p.stdin.end(stdin);
}

return;
Copy link
Contributor

Choose a reason for hiding this comment

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

It makes me nervous that we are touching this code - it has historically had very subtle issues. Did we have to for this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lukehoban ptal. Note: i recommend just looking at the after form of this function, not necessarily the diff. It's actually become quite simple and i think has a lot of behaviors that will be more desirable than what we had before.

docker/image.ts Outdated
/**
* The pushed image digest.
*/
public digest: pulumi.Output<string | undefined>;
Copy link
Contributor

Choose a reason for hiding this comment

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

These are breaking changes. Can we mitigate these by deprecating these properties and publishing something sufficiently correct for these two values?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup. i'll do that.

@CyrusNajmabadi
Copy link
Contributor Author

@lukehoban Ready for anothe rpass.

Copy link
Contributor

@lukehoban lukehoban left a comment

Choose a reason for hiding this comment

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

LGTM - just one remaining concern on the exec changes.

});

p.on("close", code => {
logStdErrMessages(code);
finish(code);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it's safe to call finish in both error and close. I believe both will frequently get called?

And it looks like we now never reject - is that really intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok. have put a lot of effort in doc'ing this. ptal @lukehoban i think the code is where we want it, and actually will give ideal results here.

docker/image.ts Outdated
*/
public registryServer: pulumi.Output<string | undefined>;

/**@deprecated This will have the same value as [imageName], but will be removed in the future. */
Copy link
Contributor

Choose a reason for hiding this comment

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

Formatting looks a little funny here - but maybe that's okay.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants