Tag built images with the commit's short hash (closes #239)
Check / check (pull_request) Successful in 5m31s
Check / check (pull_request) Successful in 5m31s
Builds are tagged upaas-<app>:<short hash>, git's short form of the commit checked out, instead of the deployment number. The clone prints the short hash and fails without one. A redeploy of a commit gives the tag to the new image. The cleanup after a deploy now also finds the app's untagged images among those its deployments recorded, and removes them by ID once the app neither runs them nor would roll back to them. It keeps every image any app uses, since apps that build the same commit can share one. commit_sha is now saved on update so manual deploys keep the commit read from the clone. Model: opus-5-5
This commit was merged in pull request #250.
This commit is contained in:
+60
-18
@@ -505,6 +505,7 @@ type cloneConfig struct {
|
||||
type CloneResult struct {
|
||||
Output string // Combined stdout/stderr from git clone
|
||||
CommitSHA string // The HEAD commit SHA after clone/checkout
|
||||
ShortSHA string // git's short form of CommitSHA (git rev-parse --short)
|
||||
}
|
||||
|
||||
// CloneRepo clones a git repository using SSH and optionally checks out a
|
||||
@@ -567,7 +568,7 @@ func (c *Client) RemoveImage(ctx context.Context, imageID ImageID) error {
|
||||
}
|
||||
|
||||
// ListImageTags returns the tags in the given repository, such as
|
||||
// "upaas-myapp:12" in "upaas-myapp", each with the ID of its image.
|
||||
// "upaas-myapp:1a2b3c4" in "upaas-myapp", each with the ID of its image.
|
||||
// Tags the same image has in other repositories are left out.
|
||||
func (c *Client) ListImageTags(
|
||||
ctx context.Context,
|
||||
@@ -597,19 +598,44 @@ func (c *Client) ListImageTags(
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// RemoveImageTag removes a tag such as "upaas-myapp:12", without force.
|
||||
// Docker then deletes the image, and the untagged images it was built on,
|
||||
// only if no other tag and no container still uses it.
|
||||
func (c *Client) RemoveImageTag(ctx context.Context, tag string) error {
|
||||
// ListUntaggedImages returns the IDs of the images that have no tag, such
|
||||
// as one whose tag a later build gave to the image it built.
|
||||
func (c *Client) ListUntaggedImages(ctx context.Context) ([]ImageID, error) {
|
||||
if c.docker == nil {
|
||||
return nil, ErrNotConnected
|
||||
}
|
||||
|
||||
images, err := c.docker.ImageList(ctx, image.ListOptions{
|
||||
Filters: filters.NewArgs(filters.Arg("dangling", "true")),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list untagged images: %w", err)
|
||||
}
|
||||
|
||||
imageIDs := make([]ImageID, 0, len(images))
|
||||
|
||||
for _, img := range images {
|
||||
imageIDs = append(imageIDs, ImageID(img.ID))
|
||||
}
|
||||
|
||||
return imageIDs, nil
|
||||
}
|
||||
|
||||
// RemoveImageTag removes the tag name, such as "upaas-myapp:1a2b3c4",
|
||||
// without force. Docker then deletes the image, and the untagged images it
|
||||
// was built on, only if no other tag and no container still uses it. If name
|
||||
// is instead the ID of an untagged image, it removes that image unless a
|
||||
// container uses it.
|
||||
func (c *Client) RemoveImageTag(ctx context.Context, name string) error {
|
||||
if c.docker == nil {
|
||||
return ErrNotConnected
|
||||
}
|
||||
|
||||
_, err := c.docker.ImageRemove(ctx, tag, image.RemoveOptions{
|
||||
_, err := c.docker.ImageRemove(ctx, name, image.RemoveOptions{
|
||||
PruneChildren: true,
|
||||
})
|
||||
if err != nil && !client.IsErrNotFound(err) {
|
||||
return fmt.Errorf("failed to remove image tag %s: %w", tag, err)
|
||||
return fmt.Errorf("failed to remove image %s: %w", name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -905,11 +931,13 @@ func (c *Client) createGitContainer(
|
||||
// Clone without depth limit so we can checkout any commit, then checkout specific SHA
|
||||
script = `git clone --branch "$CLONE_BRANCH" "$CLONE_URL" /repo` +
|
||||
` && cd /repo && git checkout "$CLONE_SHA"` +
|
||||
` && echo COMMIT:$(git rev-parse HEAD)`
|
||||
` && echo COMMIT:$(git rev-parse HEAD)` +
|
||||
` && echo SHORT_SHA:$(git rev-parse --short HEAD)`
|
||||
} else {
|
||||
// Shallow clone of branch HEAD, then output commit SHA
|
||||
script = `git clone --depth 1 --branch "$CLONE_BRANCH" "$CLONE_URL" /repo` +
|
||||
` && cd /repo && echo COMMIT:$(git rev-parse HEAD)`
|
||||
` && cd /repo && echo COMMIT:$(git rev-parse HEAD)` +
|
||||
` && echo SHORT_SHA:$(git rev-parse --short HEAD)`
|
||||
}
|
||||
|
||||
env := []string{
|
||||
@@ -987,23 +1015,37 @@ func (c *Client) runGitClone(
|
||||
)
|
||||
}
|
||||
|
||||
// Parse commit SHA from output (looks for "COMMIT:<sha>" line)
|
||||
commitSHA := parseCommitSHA(logs)
|
||||
// Parse the commit from the "COMMIT:" and "SHORT_SHA:" lines.
|
||||
result := &CloneResult{
|
||||
Output: logs,
|
||||
CommitSHA: parseCommitSHA(logs, commitMarker),
|
||||
ShortSHA: parseCommitSHA(logs, shortSHAMarker),
|
||||
}
|
||||
|
||||
return &CloneResult{Output: logs, CommitSHA: commitSHA}, nil
|
||||
// The short hash names the image the deploy builds.
|
||||
if result.ShortSHA == "" {
|
||||
return nil, fmt.Errorf("%w: no short commit hash in its output: %s",
|
||||
ErrGitCloneFailed, logs)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// commitMarker is the prefix used to identify commit SHA in clone output.
|
||||
const commitMarker = "COMMIT:"
|
||||
// Prefixes of the lines in the clone output that carry the commit checked
|
||||
// out, in full and in git's short form.
|
||||
const (
|
||||
commitMarker = "COMMIT:"
|
||||
shortSHAMarker = "SHORT_SHA:"
|
||||
)
|
||||
|
||||
// parseCommitSHA extracts the commit SHA from git clone output.
|
||||
// It looks for a line starting with "COMMIT:" and returns the SHA after it.
|
||||
func parseCommitSHA(output string) string {
|
||||
// parseCommitSHA extracts a commit SHA from git clone output.
|
||||
// It looks for a line starting with marker and returns the SHA after it.
|
||||
func parseCommitSHA(output, marker string) string {
|
||||
for line := range strings.SplitSeq(output, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
sha, found := strings.CutPrefix(line, commitMarker)
|
||||
sha, found := strings.CutPrefix(line, marker)
|
||||
if found {
|
||||
return strings.TrimSpace(sha)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user