Browse Source

Improve put-shared noop behavior and add "--force" flag for pushing operations

Tianon Gravi 7 years ago
parent
commit
42c59432d2

+ 14 - 11
bashbrew/go/src/bashbrew/cmd-push.go

@@ -18,6 +18,7 @@ func cmdPush(c *cli.Context) error {
 	uniq := c.Bool("uniq")
 	namespace := c.String("namespace")
 	dryRun := c.Bool("dry-run")
+	force := c.Bool("force")
 
 	if namespace == "" {
 		return fmt.Errorf(`"--namespace" is a required flag for "push"`)
@@ -42,18 +43,20 @@ func cmdPush(c *cli.Context) error {
 				}
 				tag = tagRepo + ":" + tag
 
-				created := dockerCreated(tag)
-				lastUpdated := fetchDockerHubTagMeta(tag).lastUpdatedTime()
-				if created.After(lastUpdated) {
-					fmt.Printf("Pushing %s\n", tag)
-					if !dryRun {
-						err = dockerPush(tag)
-						if err != nil {
-							return cli.NewMultiError(fmt.Errorf(`failed pushing %q`, tag), err)
-						}
+				if !force {
+					created := dockerCreated(tag)
+					lastUpdated := fetchDockerHubTagMeta(tag).lastUpdatedTime()
+					if !created.After(lastUpdated) {
+						fmt.Fprintf(os.Stderr, "skipping %s (created %s, last updated %s)\n", tag, created.Local().Format(time.RFC3339), lastUpdated.Local().Format(time.RFC3339))
+						continue
+					}
+				}
+				fmt.Printf("Pushing %s\n", tag)
+				if !dryRun {
+					err = dockerPush(tag)
+					if err != nil {
+						return cli.NewMultiError(fmt.Errorf(`failed pushing %q`, tag), err)
 					}
-				} else {
-					fmt.Fprintf(os.Stderr, "skipping %s (created %s, last updated %s)\n", tag, created.Local().Format(time.RFC3339), lastUpdated.Local().Format(time.RFC3339))
 				}
 			}
 		}

+ 39 - 11
bashbrew/go/src/bashbrew/cmd-put-shared.go

@@ -13,9 +13,10 @@ import (
 	"github.com/docker-library/go-dockerlibrary/manifest"
 )
 
-func entriesToManifestToolYaml(singleArch bool, r Repo, entries ...*manifest.Manifest2822Entry) (string, time.Time, error) {
+func entriesToManifestToolYaml(singleArch bool, r Repo, entries ...*manifest.Manifest2822Entry) (string, time.Time, int, error) {
 	yaml := ""
 	mru := time.Time{}
+	expectedNumber := 0
 	entryIdentifiers := []string{}
 	for _, entry := range entries {
 		entryIdentifiers = append(entryIdentifiers, r.EntryIdentifier(*entry))
@@ -45,6 +46,14 @@ func entriesToManifestToolYaml(singleArch bool, r Repo, entries ...*manifest.Man
 				mru = archU
 			}
 
+			// count up how many images we expect to push successfully in this manifest list
+			expectedNumber += len(archImageMeta.Images)
+			// for non-manifest-list tags, this will be 1 and for failed lookups it'll be 0
+			// (and if one of _these_ tags is a manifest list, we've goofed somewhere)
+			if len(archImageMeta.Images) != 1 {
+				fmt.Fprintf(os.Stderr, "warning: expected 1 image for %q; got %d\n", archImage, len(archImageMeta.Images))
+			}
+
 			yaml += fmt.Sprintf("  - image: %s\n    platform:\n", archImage)
 			yaml += fmt.Sprintf("      os: %s\n", ociArch.OS)
 			yaml += fmt.Sprintf("      architecture: %s\n", ociArch.Architecture)
@@ -54,7 +63,7 @@ func entriesToManifestToolYaml(singleArch bool, r Repo, entries ...*manifest.Man
 		}
 	}
 
-	return "manifests:\n" + yaml, mru, nil
+	return "manifests:\n" + yaml, mru, expectedNumber, nil
 }
 
 func tagsToManifestToolYaml(repo string, tags ...string) string {
@@ -76,6 +85,7 @@ func cmdPutShared(c *cli.Context) error {
 
 	namespace := c.String("namespace")
 	dryRun := c.Bool("dry-run")
+	force := c.Bool("force")
 	singleArch := c.Bool("single-arch")
 
 	if namespace == "" {
@@ -117,23 +127,41 @@ func cmdPutShared(c *cli.Context) error {
 
 		failed := []string{}
 		for _, group := range sharedTagGroups {
-			yaml, mostRecentPush, err := entriesToManifestToolYaml(singleArch, *r, group.Entries...)
+			yaml, mostRecentPush, expectedNumber, err := entriesToManifestToolYaml(singleArch, *r, group.Entries...)
 			if err != nil {
 				return err
 			}
 
+			if expectedNumber < 1 {
+				// if "expectedNumber" comes back as 0, we've probably got an API issue, so let's count up what we probably _should_ push
+				fmt.Fprintf(os.Stderr, "warning: no images expected to push for %q\n", fmt.Sprintf("%s:%s", targetRepo, group.SharedTags[0]))
+				for _, entry := range group.Entries {
+					expectedNumber += len(entry.Architectures)
+				}
+			}
+
 			tagsToPush := []string{}
 			for _, tag := range group.SharedTags {
 				image := fmt.Sprintf("%s:%s", targetRepo, tag)
-				hubMeta := fetchDockerHubTagMeta(image)
-				tagUpdated := hubMeta.lastUpdatedTime()
-				if mostRecentPush.After(tagUpdated) ||
-					(!singleArch && len(hubMeta.Images) <= 1 &&
-						(len(group.Entries) > 1 || len(group.Entries[0].Architectures) > 1)) {
-					tagsToPush = append(tagsToPush, tag)
-				} else {
-					fmt.Fprintf(os.Stderr, "skipping %s (created %s, last updated %s)\n", image, mostRecentPush.Local().Format(time.RFC3339), tagUpdated.Local().Format(time.RFC3339))
+				if !force {
+					hubMeta := fetchDockerHubTagMeta(image)
+					tagUpdated := hubMeta.lastUpdatedTime()
+					doPush := false
+					if mostRecentPush.After(tagUpdated) {
+						// if one of the images that make up the manifest list has been updated since the manifest list was last pushed, we probably need to push
+						doPush = true
+					}
+					if !singleArch && len(hubMeta.Images) != expectedNumber {
+						// if we're supposed to push more (or less) images than the current manifest list contains, we probably need to push
+						// this _should_ already be accounting for tags that haven't been pushed yet (see notes above in "entriesToManifestToolYaml" where this is calculated)
+						doPush = true
+					}
+					if !doPush {
+						fmt.Fprintf(os.Stderr, "skipping %s (created %s, last updated %s)\n", image, mostRecentPush.Local().Format(time.RFC3339), tagUpdated.Local().Format(time.RFC3339))
+						continue
+					}
 				}
+				tagsToPush = append(tagsToPush, tag)
 			}
 
 			if len(tagsToPush) == 0 {

+ 6 - 0
bashbrew/go/src/bashbrew/main.go

@@ -202,6 +202,10 @@ func main() {
 			Name:  "dry-run",
 			Usage: "do everything except the final action (for testing whether actions will be performed)",
 		},
+		"force": cli.BoolFlag{
+			Name:  "force",
+			Usage: "always push (skip the clever Hub API lookups that no-op things sooner if a push doesn't seem necessary)",
+		},
 	}
 
 	app.Commands = []cli.Command{
@@ -263,6 +267,7 @@ func main() {
 				commonFlags["uniq"],
 				commonFlags["namespace"],
 				commonFlags["dry-run"],
+				commonFlags["force"],
 			},
 			Before: subcommandBeforeFactory("push"),
 			Action: cmdPush,
@@ -274,6 +279,7 @@ func main() {
 				commonFlags["all"],
 				commonFlags["namespace"],
 				commonFlags["dry-run"],
+				commonFlags["force"],
 				cli.BoolFlag{
 					Name:  "single-arch",
 					Usage: `only act on the current architecture (for pushing "amd64/hello-world:latest", for example)`,