Bläddra i källkod

Add initial Architectures support (and the beginnings of a "put-shared" subcommand for bringing all the SharedTags and arch-specific images together with manifest lists/indexes)

Tianon Gravi 8 år sedan
förälder
incheckning
fc83c27756
21 ändrade filer med 954 tillägg och 101 borttagningar
  1. 1 1
      bashbrew/go/src/bashbrew/cmd-build.go
  2. 46 0
      bashbrew/go/src/bashbrew/cmd-put-shared.go
  3. 7 0
      bashbrew/go/src/bashbrew/config.go
  4. 4 4
      bashbrew/go/src/bashbrew/docker.go
  5. 17 17
      bashbrew/go/src/bashbrew/git.go
  6. 22 0
      bashbrew/go/src/bashbrew/main.go
  7. 9 0
      bashbrew/go/src/bashbrew/repo.go
  8. 1 1
      bashbrew/go/vendor/manifest
  9. 5 0
      bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/README.md
  10. 0 67
      bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/manifest/example.go
  11. 192 0
      bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/manifest/example_test.go
  12. 20 0
      bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/manifest/parse_test.go
  13. 239 8
      bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/manifest/rfc2822.go
  14. 38 0
      bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/manifest/testdata/bash
  15. 26 0
      bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/execpipe/execpipe_example_test.go
  16. 31 0
      bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/execpipe/execpipe_test.go
  17. 32 0
      bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/stripper/comments_example_test.go
  18. 8 0
      bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/templatelib/doc.go
  19. 18 3
      bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/templatelib/lib.go
  20. 193 0
      bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/templatelib/lib_example_test.go
  21. 45 0
      bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/templatelib/lib_test.go

+ 1 - 1
bashbrew/go/src/bashbrew/cmd-build.go

@@ -81,7 +81,7 @@ func cmdBuild(c *cli.Context) error {
 					return cli.NewMultiError(fmt.Errorf(`failed fetching git repo for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
 				}
 
-				archive, err := gitArchive(commit, entry.Directory)
+				archive, err := gitArchive(commit, entry.ArchDirectory(arch))
 				if err != nil {
 					return cli.NewMultiError(fmt.Errorf(`failed generating git archive for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
 				}

+ 46 - 0
bashbrew/go/src/bashbrew/cmd-put-shared.go

@@ -0,0 +1,46 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"path"
+	"strings"
+
+	"github.com/codegangsta/cli"
+)
+
+func cmdPutShared(c *cli.Context) error {
+	repos, err := repos(c.Bool("all"), c.Args()...)
+	if err != nil {
+		return cli.NewMultiError(fmt.Errorf(`failed gathering repo list`), err)
+	}
+
+	namespace := c.String("namespace")
+
+	if namespace == "" {
+		return fmt.Errorf(`"--namespace" is a required flag for "put-shared"`)
+	}
+
+	fmt.Fprintf(os.Stderr, "warning: this subcommand is still a big WIP -- it doesn't do anything yet!\n")
+
+	for _, repo := range repos {
+		r, err := fetch(repo)
+		if err != nil {
+			return cli.NewMultiError(fmt.Errorf(`failed fetching repo %q`, repo), err)
+		}
+
+		// TODO handle all multi-architecture tags first (regardless of whether they have SharedTags)
+
+		targetRepo := path.Join(namespace, r.RepoName)
+		for _, group := range r.Manifest.GetSharedTagGroups() {
+			// TODO build up a YAML file
+			entryTags := []string{}
+			for _, entry := range group.Entries {
+				entryTags = append(entryTags, entry.Tags[0])
+			}
+			fmt.Printf("Putting %s (tags %s) <= %s\n", targetRepo, strings.Join(group.SharedTags, ", "), strings.Join(entryTags, ", "))
+		}
+	}
+
+	return nil
+}

+ 7 - 0
bashbrew/go/src/bashbrew/config.go

@@ -18,6 +18,8 @@ type FlagsConfigEntry struct {
 
 	Commands []string `delim:"," strip:"\n\r\t "`
 
+	// TODO arch namespace mappings (for intermediate pushing before put-shared, and for put-shared to pull from to join together in one big happy family)
+
 	Library    string
 	Cache      string
 	Debug      string
@@ -26,6 +28,7 @@ type FlagsConfigEntry struct {
 	BuildOrder string
 	Pull       string
 
+	Arch                 string
 	Constraints          []string `delim:"," strip:"\n\r\t "`
 	ExclusiveConstraints string
 	ApplyConstraints     string
@@ -55,6 +58,9 @@ func (dst *FlagsConfigEntry) Apply(src FlagsConfigEntry) {
 	if src.Pull != "" {
 		dst.Pull = src.Pull
 	}
+	if src.Arch != "" {
+		dst.Arch = src.Arch
+	}
 	if len(src.Constraints) > 0 {
 		dst.Constraints = src.Constraints[:]
 	}
@@ -73,6 +79,7 @@ func (config FlagsConfigEntry) Vars() map[string]map[string]interface{} {
 			"cache":   config.Cache,
 			"debug":   config.Debug,
 
+			"arch":                  config.Arch,
 			"constraint":            config.Constraints,
 			"exclusive-constraints": config.ExclusiveConstraints,
 		},

+ 4 - 4
bashbrew/go/src/bashbrew/docker.go

@@ -24,7 +24,7 @@ func (r Repo) DockerFrom(entry *manifest.Manifest2822Entry) (string, error) {
 		return "", err
 	}
 
-	dockerfileFile := path.Join(entry.Directory, "Dockerfile")
+	dockerfileFile := path.Join(entry.ArchDirectory(arch), "Dockerfile")
 
 	cacheKey := strings.Join([]string{
 		commit,
@@ -130,9 +130,9 @@ func (r Repo) dockerBuildUniqueBits(entry *manifest.Manifest2822Entry) ([]string
 		dockerFromIdCache[from] = fromId
 	}
 	return []string{
-		entry.GitRepo,
-		entry.GitCommit,
-		entry.Directory,
+		entry.ArchGitRepo(arch),
+		entry.ArchGitCommit(arch),
+		entry.ArchDirectory(arch),
 		fromId,
 	}, nil
 }

+ 17 - 17
bashbrew/go/src/bashbrew/git.go

@@ -102,12 +102,12 @@ var gitRepoCache = map[string]string{}
 
 func (r Repo) fetchGitRepo(entry *manifest.Manifest2822Entry) (string, error) {
 	cacheKey := strings.Join([]string{
-		entry.GitRepo,
-		entry.GitFetch,
-		entry.GitCommit,
+		entry.ArchGitRepo(arch),
+		entry.ArchGitFetch(arch),
+		entry.ArchGitCommit(arch),
 	}, "\n")
 	if commit, ok := gitRepoCache[cacheKey]; ok {
-		entry.GitCommit = commit
+		entry.SetGitCommit(arch, commit)
 		return commit, nil
 	}
 
@@ -116,27 +116,27 @@ func (r Repo) fetchGitRepo(entry *manifest.Manifest2822Entry) (string, error) {
 		return "", err
 	}
 
-	if manifest.GitCommitRegex.MatchString(entry.GitCommit) {
-		commit, err := getGitCommit(entry.GitCommit)
+	if manifest.GitCommitRegex.MatchString(entry.ArchGitCommit(arch)) {
+		commit, err := getGitCommit(entry.ArchGitCommit(arch))
 		if err == nil {
 			gitRepoCache[cacheKey] = commit
-			entry.GitCommit = commit
+			entry.SetGitCommit(arch, commit)
 			return commit, nil
 		}
 	}
 
-	fetchString := entry.GitFetch + ":"
-	if entry.GitCommit == "FETCH_HEAD" {
+	fetchString := entry.ArchGitFetch(arch) + ":"
+	if entry.ArchGitCommit(arch) == "FETCH_HEAD" {
 		// fetch remote tag references to a local tag ref so that we can cache them and not re-fetch every time
 		localRef := "refs/tags/" + gitNormalizeForTagUsage(cacheKey)
 		commit, err := getGitCommit(localRef)
 		if err == nil {
 			gitRepoCache[cacheKey] = commit
-			entry.GitCommit = commit
+			entry.SetGitCommit(arch, commit)
 			return commit, nil
 		}
 		fetchString += localRef
-	} else if entry.GitFetch == manifest.DefaultLineBasedFetch {
+	} else if entry.ArchGitFetch(arch) == manifest.DefaultLineBasedFetch {
 		// backwards compat (see manifest/line-based.go in go-dockerlibrary)
 		refBase := "refs/remotes"
 		refBaseDir := filepath.Join(gitCache(), refBase)
@@ -154,17 +154,17 @@ func (r Repo) fetchGitRepo(entry *manifest.Manifest2822Entry) (string, error) {
 		// we create a temporary remote dir so that we can clean it up completely afterwards
 	}
 
-	if strings.HasPrefix(entry.GitRepo, "git://github.com/") {
-		fmt.Fprintf(os.Stderr, "warning: insecure protocol git:// detected: %s\n", entry.GitRepo)
-		entry.GitRepo = strings.Replace(entry.GitRepo, "git://", "https://", 1)
+	if strings.HasPrefix(entry.ArchGitRepo(arch), "git://github.com/") {
+		fmt.Fprintf(os.Stderr, "warning: insecure protocol git:// detected: %s\n", entry.ArchGitRepo(arch))
+		entry.SetGitRepo(arch, strings.Replace(entry.ArchGitRepo(arch), "git://", "https://", 1))
 	}
 
-	_, err = git("fetch", "--quiet", "--no-tags", entry.GitRepo, fetchString)
+	_, err = git("fetch", "--quiet", "--no-tags", entry.ArchGitRepo(arch), fetchString)
 	if err != nil {
 		return "", err
 	}
 
-	commit, err := getGitCommit(entry.GitCommit)
+	commit, err := getGitCommit(entry.ArchGitCommit(arch))
 	if err != nil {
 		return "", err
 	}
@@ -175,6 +175,6 @@ func (r Repo) fetchGitRepo(entry *manifest.Manifest2822Entry) (string, error) {
 	}
 
 	gitRepoCache[cacheKey] = commit
-	entry.GitCommit = commit
+	entry.SetGitCommit(arch, commit)
 	return commit, nil
 }

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

@@ -6,6 +6,8 @@ import (
 	"path/filepath"
 
 	"github.com/codegangsta/cli"
+
+	"github.com/docker-library/go-dockerlibrary/manifest"
 )
 
 // TODO somewhere, ensure that the Docker engine we're talking to is API version 1.22+ (Docker 1.10+)
@@ -18,6 +20,7 @@ var (
 	defaultLibrary string
 	defaultCache   string
 
+	arch                 string
 	constraints          []string
 	exclusiveConstraints bool
 
@@ -27,6 +30,7 @@ var (
 	// separated so that FlagsConfig.ApplyTo can access them
 	flagEnvVars = map[string]string{
 		"debug":   "BASHBREW_DEBUG",
+		"arch":    "BASHBREW_ARCH",
 		"config":  "BASHBREW_CONFIG",
 		"library": "BASHBREW_LIBRARY",
 		"cache":   "BASHBREW_CACHE",
@@ -72,6 +76,12 @@ func main() {
 			Usage: "do not apply any sorting, even via --build-order",
 		},
 
+		cli.StringFlag{
+			Name:   "arch",
+			Value:  manifest.DefaultArchitecture,
+			EnvVar: flagEnvVars["arch"],
+			Usage:  "the current platform architecture",
+		},
 		cli.StringSliceFlag{
 			Name:  "constraint",
 			Usage: "build constraints (see Constraints in Manifest2822Entry)",
@@ -127,6 +137,7 @@ func main() {
 			debugFlag = c.GlobalBool("debug")
 			noSortFlag = c.GlobalBool("no-sort")
 
+			arch = c.GlobalString("arch")
 			constraints = c.GlobalStringSlice("constraint")
 			exclusiveConstraints = c.GlobalBool("exclusive-constraints")
 
@@ -222,7 +233,18 @@ func main() {
 			Before: subcommandBeforeFactory("push"),
 			Action: cmdPush,
 		},
+		{
+			Name:  "put-shared",
+			Usage: `updated shared tags in the registry`,
+			Flags: []cli.Flag{
+				commonFlags["all"],
+				commonFlags["namespace"],
+			},
+			Before: subcommandBeforeFactory("put-shared"),
+			Action: cmdPutShared,
+		},
 
+		// TODO --depth flag for children and parents
 		{
 			Name: "children",
 			Aliases: []string{

+ 9 - 0
bashbrew/go/src/bashbrew/repo.go

@@ -79,6 +79,15 @@ var haveOutputSkippedMessage = map[string]bool{}
 func (r Repo) SkipConstraints(entry manifest.Manifest2822Entry) bool {
 	repoTag := r.RepoName + ":" + entry.Tags[0]
 
+	// TODO decide if "arch" and "constraints" should be handled separately (but probably not)
+	if !entry.HasArchitecture(arch) {
+		if !haveOutputSkippedMessage[repoTag] {
+			fmt.Fprintf(os.Stderr, "skipping %q (due to architecture %q; only %q supported)\n", repoTag, arch, entry.ArchitecturesString())
+			haveOutputSkippedMessage[repoTag] = true
+		}
+		return true
+	}
+
 	if len(entry.Constraints) == 0 {
 		if exclusiveConstraints {
 			if !haveOutputSkippedMessage[repoTag] {

+ 1 - 1
bashbrew/go/vendor/manifest

@@ -10,7 +10,7 @@
 		{
 			"importpath": "github.com/docker-library/go-dockerlibrary",
 			"repository": "https://github.com/docker-library/go-dockerlibrary",
-			"revision": "08ef5a968ebdd83dcc42998a96b6528837b55273",
+			"revision": "663a091da13fc848e27a16048fb39c4e4067056e",
 			"branch": "master"
 		},
 		{

+ 5 - 0
bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/README.md

@@ -0,0 +1,5 @@
+# `import "github.com/docker-library/go-dockerlibrary/manifest"`
+
+[![Travis Build Status](https://travis-ci.org/docker-library/go-dockerlibrary.svg?branch=master)](https://travis-ci.org/docker-library/go-dockerlibrary) [![GoDoc](https://godoc.org/github.com/docker-library/go-dockerlibrary?status.svg)](https://godoc.org/github.com/docker-library/go-dockerlibrary) [![codecov](https://codecov.io/gh/docker-library/go-dockerlibrary/branch/master/graph/badge.svg)](https://codecov.io/gh/docker-library/go-dockerlibrary)
+
+This package contains the core parsing elements of [the `bashbrew` tool used by the Docker Official Images](https://github.com/docker-library/official-images/tree/master/bashbrew).

+ 0 - 67
bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/manifest/example.go

@@ -1,67 +0,0 @@
-// +build ignore
-
-package main
-
-import (
-	"bufio"
-	"fmt"
-	"strings"
-
-	"github.com/docker-library/go-dockerlibrary/manifest"
-)
-
-func main() {
-	// TODO comment parsing
-	man, err := manifest.Parse(bufio.NewReader(strings.NewReader(`# RFC 2822
-
-	# I LOVE CAKE
-
-Maintainers: InfoSiftr <[email protected]> (@infosiftr),
-             Johan Euphrosine <[email protected]> (@proppy)
-GitRepo: https://github.com/docker-library/golang.git
-GitFetch: refs/heads/master
-
-
- # hi
-
-
- 	 # blasphemer
-
-
-# Go 1.6
-Tags: 1.6.1, 1.6, 1, latest
-GitCommit: 0ce80411b9f41e9c3a21fc0a1bffba6ae761825a
-Directory: 1.6
-
-
-# Go 1.5
-Tags: 1.5.3
-GitCommit: d7e2a8d90a9b8f5dfd5bcd428e0c33b68c40cc19
-Directory: 1.5
-
-
-Tags: 1.5
-GitCommit: d7e2a8d90a9b8f5dfd5bcd428e0c33b68c40cc19
-Directory: 1.5
-
-
-`)))
-	if err != nil {
-		panic(err)
-	}
-	fmt.Printf("-------------\n2822:\n%s\n", man)
-
-	man, err = manifest.Parse(bufio.NewReader(strings.NewReader(`
-# first set
-a: b@c d
-e: b@c d
-
- # second set
-f: g@h
-i: g@h j
-`)))
-	if err != nil {
-		panic(err)
-	}
-	fmt.Printf("-------------\nline-based:\n%v\n", man)
-}

+ 192 - 0
bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/manifest/example_test.go

@@ -0,0 +1,192 @@
+package manifest_test
+
+import (
+	"bufio"
+	"fmt"
+	"strings"
+
+	"github.com/docker-library/go-dockerlibrary/manifest"
+)
+
+func Example() {
+	man, err := manifest.Parse(bufio.NewReader(strings.NewReader(`# RFC 2822
+
+	# I LOVE CAKE
+
+Maintainers: InfoSiftr <[email protected]> (@infosiftr),
+             Johan Euphrosine <[email protected]> (@proppy)
+GitFetch: refs/heads/master
+GitRepo: https://github.com/docker-library/golang.git
+SharedTags: latest
+arm64v8-GitRepo: https://github.com/docker-library/golang.git
+Architectures: amd64
+
+
+ # hi
+
+
+ 	 # blasphemer
+
+
+# Go 1.6
+Tags: 1.6.1, 1.6, 1
+arm64v8-GitRepo: https://github.com/docker-library/golang.git
+Directory: 1.6
+GitCommit: 0ce80411b9f41e9c3a21fc0a1bffba6ae761825a
+Constraints: some-random-build-server
+
+
+# Go 1.5
+Tags: 1.5.3
+GitCommit: d7e2a8d90a9b8f5dfd5bcd428e0c33b68c40cc19
+SharedTags: 1.5.3-debian, 1.5-debian
+Directory: 1.5
+s390x-GitCommit: b6c460e7cd79b595267870a98013ec3078b490df
+i386-GitFetch: refs/heads/i386
+ppc64le-Directory: 1.5/ppc64le
+
+
+Tags: 1.5
+SharedTags: 1.5-debian
+GitCommit: d7e2a8d90a9b8f5dfd5bcd428e0c33b68c40cc19
+Directory: 1.5
+s390x-GitCommit: b6c460e7cd79b595267870a98013ec3078b490df
+i386-GitFetch: refs/heads/i386
+ppc64le-Directory: 1.5/ppc64le
+
+SharedTags: raspbian
+GitCommit: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef
+Tags: raspbian-s390x
+Architectures: s390x
+
+
+`)))
+	if err != nil {
+		panic(err)
+	}
+	fmt.Printf("-------------\n2822:\n%s\n", man)
+
+	fmt.Printf("\nShared Tag Groups:\n")
+	for _, group := range man.GetSharedTagGroups() {
+		fmt.Printf("\n  - %s\n", strings.Join(group.SharedTags, ", "))
+		for _, entry := range group.Entries {
+			fmt.Printf("    - %s\n", entry.TagsString())
+		}
+	}
+	fmt.Printf("\n")
+
+	man, err = manifest.Parse(bufio.NewReader(strings.NewReader(`
+# maintainer: InfoSiftr <[email protected]> (@infosiftr)
+# maintainer: John Smith <[email protected]> (@example-jsmith)
+
+# first set
+a: b@c d
+e: b@c d
+
+ # second set
+f: g@h
+i: g@h j
+`)))
+	if err != nil {
+		panic(err)
+	}
+	fmt.Printf("-------------\nline-based:\n%v\n", man)
+
+	// Output:
+	// -------------
+	// 2822:
+	// Maintainers: InfoSiftr <[email protected]> (@infosiftr), Johan Euphrosine <[email protected]> (@proppy)
+	// SharedTags: latest
+	// GitRepo: https://github.com/docker-library/golang.git
+	// arm64v8-GitRepo: https://github.com/docker-library/golang.git
+	//
+	// Tags: 1.6.1, 1.6, 1
+	// GitCommit: 0ce80411b9f41e9c3a21fc0a1bffba6ae761825a
+	// Directory: 1.6
+	// Constraints: some-random-build-server
+	//
+	// Tags: 1.5.3, 1.5
+	// SharedTags: 1.5.3-debian, 1.5-debian
+	// GitCommit: d7e2a8d90a9b8f5dfd5bcd428e0c33b68c40cc19
+	// Directory: 1.5
+	// i386-GitFetch: refs/heads/i386
+	// ppc64le-Directory: 1.5/ppc64le
+	// s390x-GitCommit: b6c460e7cd79b595267870a98013ec3078b490df
+	//
+	// Tags: raspbian-s390x
+	// SharedTags: raspbian
+	// Architectures: s390x
+	// GitCommit: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef
+	//
+	// Shared Tag Groups:
+	//
+	//   - latest
+	//     - 1.6.1, 1.6, 1
+	//
+	//   - 1.5.3-debian, 1.5-debian
+	//     - 1.5.3, 1.5
+	//
+	//   - raspbian
+	//     - raspbian-s390x
+	//
+	// -------------
+	// line-based:
+	// Maintainers: InfoSiftr <[email protected]> (@infosiftr), John Smith <[email protected]> (@example-jsmith)
+	// GitFetch: refs/heads/*
+	//
+	// Tags: a, e
+	// GitRepo: b
+	// GitCommit: c
+	// Directory: d
+	//
+	// Tags: f
+	// GitRepo: g
+	// GitFetch: refs/tags/h
+	// GitCommit: FETCH_HEAD
+	//
+	// Tags: i
+	// GitRepo: g
+	// GitFetch: refs/tags/h
+	// GitCommit: FETCH_HEAD
+	// Directory: j
+}
+
+func ExampleFetch_local() {
+	repoName, tagName, man, err := manifest.Fetch("testdata", "bash:4.4")
+	if err != nil {
+		panic(err)
+	}
+
+	fmt.Printf("%s:%s\n\n", repoName, tagName)
+
+	fmt.Println(man.GetTag(tagName).ClearDefaults(manifest.DefaultManifestEntry).String())
+
+	// Output:
+	// bash:4.4
+	//
+	// Maintainers: Tianon Gravi <[email protected]> (@tianon)
+	// Tags: 4.4.12, 4.4, 4, latest
+	// GitRepo: https://github.com/tianon/docker-bash.git
+	// GitCommit: 1cbb5cf49b4c53bd5a986abf7a1afeb9a80eac1e
+	// Directory: 4.4
+}
+
+func ExampleFetch_remote() {
+	repoName, tagName, man, err := manifest.Fetch("/home/jsmith/docker/official-images/library", "https://github.com/docker-library/official-images/raw/1a3c4cd6d5cd53bd538a6f56a69f94c5b35325a7/library/bash:4.4")
+	if err != nil {
+		panic(err)
+	}
+
+	fmt.Printf("%s:%s\n\n", repoName, tagName)
+
+	fmt.Println(man.GetTag(tagName).ClearDefaults(manifest.DefaultManifestEntry).String())
+
+	// Output:
+	// bash:4.4
+	//
+	// Maintainers: Tianon Gravi <[email protected]> (@tianon)
+	// Tags: 4.4.12, 4.4, 4, latest
+	// GitRepo: https://github.com/tianon/docker-bash.git
+	// GitCommit: 1cbb5cf49b4c53bd5a986abf7a1afeb9a80eac1e
+	// Directory: 4.4
+}

+ 20 - 0
bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/manifest/parse_test.go

@@ -0,0 +1,20 @@
+package manifest_test
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/docker-library/go-dockerlibrary/manifest"
+)
+
+func TestParseError(t *testing.T) {
+	invalidManifest := `this is just completely bogus and invalid no matter how you slice it`
+
+	man, err := manifest.Parse(strings.NewReader(invalidManifest))
+	if err == nil {
+		t.Errorf("Expected error, got valid manifest instead:\n%s", man)
+	}
+	if !strings.HasPrefix(err.Error(), "cannot parse manifest in either format:") {
+		t.Errorf("Unexpected error: %v", err)
+	}
+}

+ 239 - 8
bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/manifest/rfc2822.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"io"
 	"regexp"
+	"sort"
 	"strings"
 
 	"github.com/docker-library/go-dockerlibrary/pkg/stripper"
@@ -27,25 +28,37 @@ type Manifest2822Entry struct {
 
 	Maintainers []string `delim:"," strip:"\n\r\t "`
 
-	Tags []string `delim:"," strip:"\n\r\t "`
+	Tags       []string `delim:"," strip:"\n\r\t "`
+	SharedTags []string `delim:"," strip:"\n\r\t "`
+
+	Architectures []string `delim:"," strip:"\n\r\t "`
 
 	GitRepo   string
 	GitFetch  string
 	GitCommit string
 	Directory string
+	// architecture-specific versions of the above fields are in Paragraph.Values as ARCH-Field, ala s390x-Directory
 
 	Constraints []string `delim:"," strip:"\n\r\t "`
 }
 
-var DefaultManifestEntry = Manifest2822Entry{
-	GitFetch:  "refs/heads/master",
-	Directory: ".",
-}
+var (
+	DefaultArchitecture = "amd64"
+
+	DefaultManifestEntry = Manifest2822Entry{
+		Architectures: []string{DefaultArchitecture},
+
+		GitFetch:  "refs/heads/master",
+		Directory: ".",
+	}
+)
 
 func (entry Manifest2822Entry) Clone() Manifest2822Entry {
 	// SLICES! grr
 	entry.Maintainers = append([]string{}, entry.Maintainers...)
 	entry.Tags = append([]string{}, entry.Tags...)
+	entry.SharedTags = append([]string{}, entry.SharedTags...)
+	entry.Architectures = append([]string{}, entry.Architectures...)
 	entry.Constraints = append([]string{}, entry.Constraints...)
 	return entry
 }
@@ -60,23 +73,60 @@ func (entry Manifest2822Entry) TagsString() string {
 	return strings.Join(entry.Tags, StringSeparator2822)
 }
 
+func (entry Manifest2822Entry) SharedTagsString() string {
+	return strings.Join(entry.SharedTags, StringSeparator2822)
+}
+
+func (entry Manifest2822Entry) ArchitecturesString() string {
+	return strings.Join(entry.Architectures, StringSeparator2822)
+}
+
 func (entry Manifest2822Entry) ConstraintsString() string {
 	return strings.Join(entry.Constraints, StringSeparator2822)
 }
 
 // if this method returns "true", then a.Tags and b.Tags can safely be combined (for the purposes of building)
 func (a Manifest2822Entry) SameBuildArtifacts(b Manifest2822Entry) bool {
-	return a.GitRepo == b.GitRepo && a.GitFetch == b.GitFetch && a.GitCommit == b.GitCommit && a.Directory == b.Directory && a.ConstraintsString() == b.ConstraintsString()
+	// check xxxarch-GitRepo, etc. fields for sameness first
+	for _, key := range append(a.archFields(), b.archFields()...) {
+		if a.Paragraph.Values[key] != b.Paragraph.Values[key] {
+			return false
+		}
+	}
+
+	return a.ArchitecturesString() == b.ArchitecturesString() && a.GitRepo == b.GitRepo && a.GitFetch == b.GitFetch && a.GitCommit == b.GitCommit && a.Directory == b.Directory && a.ConstraintsString() == b.ConstraintsString()
+}
+
+func isArchField(field string) bool {
+	return strings.HasSuffix(field, "-GitRepo") || strings.HasSuffix(field, "-GitFetch") || strings.HasSuffix(field, "-GitCommit") || strings.HasSuffix(field, "-Directory")
+}
+
+// returns a list of architecture-specific fields in an Entry
+func (entry Manifest2822Entry) archFields() []string {
+	ret := []string{}
+	for key, val := range entry.Paragraph.Values {
+		if isArchField(key) && val != "" {
+			ret = append(ret, key)
+		}
+	}
+	return ret
 }
 
 // returns a new Entry with any of the values that are equal to the values in "defaults" cleared
 func (entry Manifest2822Entry) ClearDefaults(defaults Manifest2822Entry) Manifest2822Entry {
+	entry = entry.Clone() // make absolutely certain we have a deep clone
 	if entry.MaintainersString() == defaults.MaintainersString() {
 		entry.Maintainers = nil
 	}
 	if entry.TagsString() == defaults.TagsString() {
 		entry.Tags = nil
 	}
+	if entry.SharedTagsString() == defaults.SharedTagsString() {
+		entry.SharedTags = nil
+	}
+	if entry.ArchitecturesString() == defaults.ArchitecturesString() {
+		entry.Architectures = nil
+	}
 	if entry.GitRepo == defaults.GitRepo {
 		entry.GitRepo = ""
 	}
@@ -89,6 +139,11 @@ func (entry Manifest2822Entry) ClearDefaults(defaults Manifest2822Entry) Manifes
 	if entry.Directory == defaults.Directory {
 		entry.Directory = ""
 	}
+	for _, key := range defaults.archFields() {
+		if defaults.Paragraph.Values[key] == entry.Paragraph.Values[key] {
+			delete(entry.Paragraph.Values, key)
+		}
+	}
 	if entry.ConstraintsString() == defaults.ConstraintsString() {
 		entry.Constraints = nil
 	}
@@ -103,6 +158,12 @@ func (entry Manifest2822Entry) String() string {
 	if str := entry.TagsString(); str != "" {
 		ret = append(ret, "Tags: "+str)
 	}
+	if str := entry.SharedTagsString(); str != "" {
+		ret = append(ret, "SharedTags: "+str)
+	}
+	if str := entry.ArchitecturesString(); str != "" {
+		ret = append(ret, "Architectures: "+str)
+	}
 	if str := entry.GitRepo; str != "" {
 		ret = append(ret, "GitRepo: "+str)
 	}
@@ -115,6 +176,11 @@ func (entry Manifest2822Entry) String() string {
 	if str := entry.Directory; str != "" {
 		ret = append(ret, "Directory: "+str)
 	}
+	archFields := entry.archFields()
+	sort.Strings(archFields) // consistent ordering
+	for _, key := range archFields {
+		ret = append(ret, key+": "+entry.Paragraph.Values[key])
+	}
 	if str := entry.ConstraintsString(); str != "" {
 		ret = append(ret, "Constraints: "+str)
 	}
@@ -136,6 +202,48 @@ func (manifest Manifest2822) String() string {
 	return strings.Join(ret, "\n\n")
 }
 
+func (entry *Manifest2822Entry) SetGitRepo(arch string, repo string) {
+	if entry.Paragraph.Values == nil {
+		entry.Paragraph.Values = map[string]string{}
+	}
+	entry.Paragraph.Values[arch+"-GitRepo"] = repo
+}
+
+func (entry Manifest2822Entry) ArchGitRepo(arch string) string {
+	if val, ok := entry.Paragraph.Values[arch+"-GitRepo"]; ok && val != "" {
+		return val
+	}
+	return entry.GitRepo
+}
+
+func (entry Manifest2822Entry) ArchGitFetch(arch string) string {
+	if val, ok := entry.Paragraph.Values[arch+"-GitFetch"]; ok && val != "" {
+		return val
+	}
+	return entry.GitFetch
+}
+
+func (entry *Manifest2822Entry) SetGitCommit(arch string, commit string) {
+	if entry.Paragraph.Values == nil {
+		entry.Paragraph.Values = map[string]string{}
+	}
+	entry.Paragraph.Values[arch+"-GitCommit"] = commit
+}
+
+func (entry Manifest2822Entry) ArchGitCommit(arch string) string {
+	if val, ok := entry.Paragraph.Values[arch+"-GitCommit"]; ok && val != "" {
+		return val
+	}
+	return entry.GitCommit
+}
+
+func (entry Manifest2822Entry) ArchDirectory(arch string) string {
+	if val, ok := entry.Paragraph.Values[arch+"-Directory"]; ok && val != "" {
+		return val
+	}
+	return entry.Directory
+}
+
 func (entry Manifest2822Entry) HasTag(tag string) bool {
 	for _, existingTag := range entry.Tags {
 		if tag == existingTag {
@@ -145,6 +253,26 @@ func (entry Manifest2822Entry) HasTag(tag string) bool {
 	return false
 }
 
+// HasSharedTag returns true if the given tag exists in entry.SharedTags.
+func (entry Manifest2822Entry) HasSharedTag(tag string) bool {
+	for _, existingTag := range entry.SharedTags {
+		if tag == existingTag {
+			return true
+		}
+	}
+	return false
+}
+
+// HasArchitecture returns true if the given architecture exists in entry.Architectures
+func (entry Manifest2822Entry) HasArchitecture(arch string) bool {
+	for _, existingArch := range entry.Architectures {
+		if arch == existingArch {
+			return true
+		}
+	}
+	return false
+}
+
 func (manifest Manifest2822) GetTag(tag string) *Manifest2822Entry {
 	for _, entry := range manifest.Entries {
 		if entry.HasTag(tag) {
@@ -154,6 +282,62 @@ func (manifest Manifest2822) GetTag(tag string) *Manifest2822Entry {
 	return nil
 }
 
+// GetSharedTag returns a list of entries with the given tag in entry.SharedTags (or the empty list if there are no entries with the given tag).
+func (manifest Manifest2822) GetSharedTag(tag string) []Manifest2822Entry {
+	ret := []Manifest2822Entry{}
+	for _, entry := range manifest.Entries {
+		if entry.HasSharedTag(tag) {
+			ret = append(ret, entry)
+		}
+	}
+	return ret
+}
+
+// GetAllSharedTags returns a list of the sum of all SharedTags in all entries of this image manifest (in the order they appear in the file).
+func (manifest Manifest2822) GetAllSharedTags() []string {
+	fakeEntry := Manifest2822Entry{}
+	for _, entry := range manifest.Entries {
+		fakeEntry.SharedTags = append(fakeEntry.SharedTags, entry.SharedTags...)
+	}
+	fakeEntry.DeduplicateSharedTags()
+	return fakeEntry.SharedTags
+}
+
+type SharedTagGroup struct {
+	SharedTags []string
+	Entries    []*Manifest2822Entry
+}
+
+// GetSharedTagGroups returns a map of shared tag groups to the list of entries they share (as described in https://github.com/docker-library/go-dockerlibrary/pull/2#issuecomment-277853597).
+func (manifest Manifest2822) GetSharedTagGroups() []SharedTagGroup {
+	inter := map[string][]string{}
+	interOrder := []string{} // order matters, and maps randomize order
+	interKeySep := ","
+	for _, sharedTag := range manifest.GetAllSharedTags() {
+		interKeyParts := []string{}
+		for _, entry := range manifest.GetSharedTag(sharedTag) {
+			interKeyParts = append(interKeyParts, entry.Tags[0])
+		}
+		interKey := strings.Join(interKeyParts, interKeySep)
+		if _, ok := inter[interKey]; !ok {
+			interOrder = append(interOrder, interKey)
+		}
+		inter[interKey] = append(inter[interKey], sharedTag)
+	}
+	ret := []SharedTagGroup{}
+	for _, tags := range interOrder {
+		group := SharedTagGroup{
+			SharedTags: inter[tags],
+			Entries:    []*Manifest2822Entry{},
+		}
+		for _, tag := range strings.Split(tags, interKeySep) {
+			group.Entries = append(group.Entries, manifest.GetTag(tag))
+		}
+		ret = append(ret, group)
+	}
+	return ret
+}
+
 func (manifest *Manifest2822) AddEntry(entry Manifest2822Entry) error {
 	if len(entry.Tags) < 1 {
 		return fmt.Errorf("missing Tags")
@@ -165,20 +349,36 @@ func (manifest *Manifest2822) AddEntry(entry Manifest2822Entry) error {
 		return fmt.Errorf("Tags %q has invalid Maintainers: %q (expected format %q)", strings.Join(invalidMaintainers, ", "), MaintainersFormat)
 	}
 
+	entry.DeduplicateSharedTags()
+
 	seenTag := map[string]bool{}
 	for _, tag := range entry.Tags {
 		if otherEntry := manifest.GetTag(tag); otherEntry != nil {
 			return fmt.Errorf("Tags %q includes duplicate tag: %q (duplicated in %q)", entry.TagsString(), tag, otherEntry.TagsString())
 		}
+		if otherEntries := manifest.GetSharedTag(tag); len(otherEntries) > 0 {
+			return fmt.Errorf("Tags %q includes tag conflicting with a shared tag: %q (shared tag in %q)", entry.TagsString(), tag, otherEntries[0].TagsString())
+		}
 		if seenTag[tag] {
 			return fmt.Errorf("Tags %q includes duplicate tag: %q", entry.TagsString(), tag)
 		}
 		seenTag[tag] = true
 	}
+	for _, tag := range entry.SharedTags {
+		if otherEntry := manifest.GetTag(tag); otherEntry != nil {
+			return fmt.Errorf("Tags %q includes conflicting shared tag: %q (duplicated in %q)", entry.TagsString(), tag, otherEntry.TagsString())
+		}
+		if seenTag[tag] {
+			return fmt.Errorf("Tags %q includes duplicate tag: %q (in SharedTags)", entry.TagsString(), tag)
+		}
+		seenTag[tag] = true
+	}
 
 	for i, existingEntry := range manifest.Entries {
 		if existingEntry.SameBuildArtifacts(entry) {
 			manifest.Entries[i].Tags = append(existingEntry.Tags, entry.Tags...)
+			manifest.Entries[i].SharedTags = append(existingEntry.SharedTags, entry.SharedTags...)
+			manifest.Entries[i].DeduplicateSharedTags()
 			return nil
 		}
 	}
@@ -210,20 +410,51 @@ func (entry Manifest2822Entry) InvalidMaintainers() []string {
 	return invalid
 }
 
+// DeduplicateSharedTags will remove duplicate values from entry.SharedTags, preserving order.
+func (entry *Manifest2822Entry) DeduplicateSharedTags() {
+	aggregate := []string{}
+	seen := map[string]bool{}
+	for _, tag := range entry.SharedTags {
+		if seen[tag] {
+			continue
+		}
+		seen[tag] = true
+		aggregate = append(aggregate, tag)
+	}
+	entry.SharedTags = aggregate
+}
+
 type decoderWrapper struct {
 	*control.Decoder
 }
 
 func (decoder *decoderWrapper) Decode(entry *Manifest2822Entry) error {
+	// reset Architectures and SharedTags so that they can be either inherited or replaced, not additive
+	sharedTags := entry.SharedTags
+	entry.SharedTags = nil
+	arches := entry.Architectures
+	entry.Architectures = nil
+
 	for {
 		err := decoder.Decoder.Decode(entry)
 		if err != nil {
 			return err
 		}
+
 		// ignore empty paragraphs (blank lines at the start, excess blank lines between paragraphs, excess blank lines at EOF)
-		if len(entry.Paragraph.Order) > 0 {
-			return nil
+		if len(entry.Paragraph.Order) == 0 {
+			continue
+		}
+
+		// if we had no SharedTags or Architectures, restore our "default" (original) values
+		if len(entry.SharedTags) == 0 {
+			entry.SharedTags = sharedTags
+		}
+		if len(entry.Architectures) == 0 {
+			entry.Architectures = arches
 		}
+
+		return nil
 	}
 }
 

+ 38 - 0
bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/manifest/testdata/bash

@@ -0,0 +1,38 @@
+# this is a snapshot of https://github.com/docker-library/official-images/raw/1a3c4cd6d5cd53bd538a6f56a69f94c5b35325a7/library/bash
+
+# this file is generated via https://github.com/tianon/docker-bash/blob/cd1de3dfc885b3395cd354ddb988922350b092a7/generate-stackbrew-library.sh
+
+Maintainers: Tianon Gravi <[email protected]> (@tianon)
+GitRepo: https://github.com/tianon/docker-bash.git
+
+Tags: 4.4.12, 4.4, 4, latest
+GitCommit: 1cbb5cf49b4c53bd5a986abf7a1afeb9a80eac1e
+Directory: 4.4
+
+Tags: 4.3.48, 4.3
+GitCommit: 1cbb5cf49b4c53bd5a986abf7a1afeb9a80eac1e
+Directory: 4.3
+
+Tags: 4.2.53, 4.2
+GitCommit: 1cbb5cf49b4c53bd5a986abf7a1afeb9a80eac1e
+Directory: 4.2
+
+Tags: 4.1.17, 4.1
+GitCommit: 1cbb5cf49b4c53bd5a986abf7a1afeb9a80eac1e
+Directory: 4.1
+
+Tags: 4.0.44, 4.0
+GitCommit: 4438745d601d10d300e363f24205a3ca75307803
+Directory: 4.0
+
+Tags: 3.2.57, 3.2, 3
+GitCommit: 1cbb5cf49b4c53bd5a986abf7a1afeb9a80eac1e
+Directory: 3.2
+
+Tags: 3.1.23, 3.1
+GitCommit: 1cbb5cf49b4c53bd5a986abf7a1afeb9a80eac1e
+Directory: 3.1
+
+Tags: 3.0.22, 3.0
+GitCommit: 1cbb5cf49b4c53bd5a986abf7a1afeb9a80eac1e
+Directory: 3.0

+ 26 - 0
bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/execpipe/execpipe_example_test.go

@@ -0,0 +1,26 @@
+package execpipe_test
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"strings"
+
+	"github.com/docker-library/go-dockerlibrary/pkg/execpipe"
+)
+
+func Example() {
+	pipe, err := execpipe.RunCommand("go", "version")
+	if err != nil {
+		panic(err)
+	}
+	defer pipe.Close()
+
+	var buf bytes.Buffer
+	io.Copy(&buf, pipe)
+
+	fmt.Println(strings.SplitN(buf.String(), " version ", 2)[0])
+
+	// Output:
+	// go
+}

+ 31 - 0
bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/execpipe/execpipe_test.go

@@ -0,0 +1,31 @@
+package execpipe_test
+
+import (
+	"os"
+	"os/exec"
+	"testing"
+
+	"github.com/docker-library/go-dockerlibrary/pkg/execpipe"
+)
+
+func TestStdoutPipeError(t *testing.T) {
+	cmd := exec.Command("nothing", "really", "matters", "in", "the", "end")
+
+	// set "Stdout" so that "cmd.StdoutPipe" fails
+	// https://golang.org/src/os/exec/exec.go?s=16834:16883#L587
+	cmd.Stdout = os.Stdout
+
+	_, err := execpipe.Run(cmd)
+	if err == nil {
+		t.Errorf("Expected execpipe.Run to fail -- it did not")
+	}
+}
+
+func TestStartError(t *testing.T) {
+	// craft a definitely-invalid command so that "cmd.Start" fails
+	// https://golang.org/src/os/exec/exec.go?s=8739:8766#L303
+	_, err := execpipe.RunCommand("nothing-really-matters-in-the-end--bogus-command")
+	if err == nil {
+		t.Errorf("Expected execpipe.RunCommand to fail -- it did not")
+	}
+}

+ 32 - 0
bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/stripper/comments_example_test.go

@@ -0,0 +1,32 @@
+package stripper_test
+
+import (
+	"io"
+	"os"
+	"strings"
+
+	"github.com/docker-library/go-dockerlibrary/pkg/stripper"
+)
+
+func ExampleCommentStripper() {
+	r := strings.NewReader(`
+# opening comment
+a: b
+# comment!
+c: d # not a comment
+
+# another cheeky comment
+e: f
+`)
+
+	comStrip := stripper.NewCommentStripper(r)
+
+	// using CopyBuffer to force smaller Read sizes (better testing coverage that way)
+	io.CopyBuffer(os.Stdout, comStrip, make([]byte, 32))
+
+	// Output:
+	// a: b
+	// c: d # not a comment
+	//
+	// e: f
+}

+ 8 - 0
bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/templatelib/doc.go

@@ -0,0 +1,8 @@
+/*
+Package templatelib implements a group of useful functions for use with the stdlib text/template package.
+
+Usage:
+
+	tmpl, err := template.New("some-template").Funcs(templatelib.FuncMap).Parse("Hi, {{ join " " .Names }}")
+*/
+package templatelib

+ 18 - 3
bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/templatelib/lib.go

@@ -67,12 +67,15 @@ func stringsModifierActionFactory(a func(string, string) string) func([]string,
 	}
 }
 
-// TODO write some tests for these
-
 var FuncMap = template.FuncMap{
+	// {{- $isGitHub := hasPrefix "https://github.com/" $url -}}
+	// {{- $isHtml := hasSuffix ".html" $url -}}
 	"hasPrefix": swapStringsFuncBoolArgsOrder(strings.HasPrefix),
 	"hasSuffix": swapStringsFuncBoolArgsOrder(strings.HasSuffix),
 
+	// {{- $hugeIfTrue := .SomeValue | ternary "HUGE" "not so huge" -}}
+	// if .SomeValue is truthy, $hugeIfTrue will be "HUGE"
+	// (otherwise, "not so huge")
 	"ternary": func(truthy interface{}, falsey interface{}, val interface{}) interface{} {
 		if t, ok := template.IsTrue(val); !ok {
 			panic(fmt.Sprintf(`template.IsTrue(%+v) says things are NOT OK`, val))
@@ -83,14 +86,26 @@ var FuncMap = template.FuncMap{
 		}
 	},
 
+	// First Tag: {{- .Tags | first -}}
+	// Last Tag:  {{- .Tags | last -}}
 	"first": thingsActionFactory("first", true, func(args []interface{}, arg interface{}) interface{} { return arg }),
 	"last":  thingsActionFactory("last", false, func(args []interface{}, arg interface{}) interface{} { return arg }),
 
+	// JSON data dump: {{ json . }}
+	// (especially nice for taking data and piping it to "jq")
+	// (ie "some-tool inspect --format '{{ json . }}' some-things | jq .")
 	"json": func(v interface{}) (string, error) {
 		j, err := json.Marshal(v)
 		return string(j), err
 	},
-	"join":         stringsActionFactory("join", true, strings.Join),
+
+	// Everybody: {{- join ", " .Names -}}
+	// Concat: {{- join "/" "https://github.com" "jsmith" "some-repo" -}}
+	"join": stringsActionFactory("join", true, strings.Join),
+
+	// {{- $mungedUrl := $url | replace "git://" "https://" | trimSuffixes ".git" -}}
+	// turns: git://github.com/jsmith/some-repo.git
+	// into: https://github.com/jsmith/some-repo
 	"trimPrefixes": stringsActionFactory("trimPrefixes", false, stringsModifierActionFactory(strings.TrimPrefix)),
 	"trimSuffixes": stringsActionFactory("trimSuffixes", false, stringsModifierActionFactory(strings.TrimSuffix)),
 	"replace": stringsActionFactory("replace", false, func(strs []string, str string) string {

+ 193 - 0
bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/templatelib/lib_example_test.go

@@ -0,0 +1,193 @@
+package templatelib_test
+
+import (
+	"os"
+	"text/template"
+
+	"github.com/docker-library/go-dockerlibrary/pkg/templatelib"
+)
+
+func Example_prefixSuffix() {
+	tmpl, err := template.New("github-or-html").Funcs(templatelib.FuncMap).Parse(`
+		{{- . -}}
+
+		{{- if hasPrefix "https://github.com/" . -}}
+			{{- " " -}} GitHub
+		{{- end -}}
+
+		{{- if hasSuffix ".html" . -}}
+			{{- " " -}} HTML
+		{{- end -}}
+
+		{{- "\n" -}}
+	`)
+	if err != nil {
+		panic(err)
+	}
+
+	err = tmpl.Execute(os.Stdout, "https://github.com/example/example")
+	if err != nil {
+		panic(err)
+	}
+
+	err = tmpl.Execute(os.Stdout, "https://example.com/test.html")
+	if err != nil {
+		panic(err)
+	}
+
+	err = tmpl.Execute(os.Stdout, "https://example.com")
+	if err != nil {
+		panic(err)
+	}
+
+	err = tmpl.Execute(os.Stdout, "https://github.com/example/example/raw/master/test.html")
+	if err != nil {
+		panic(err)
+	}
+
+	// Output:
+	// https://github.com/example/example GitHub
+	// https://example.com/test.html HTML
+	// https://example.com
+	// https://github.com/example/example/raw/master/test.html GitHub HTML
+}
+
+func Example_ternary() {
+	tmpl, err := template.New("huge-if-true").Funcs(templatelib.FuncMap).Parse(`
+		{{- range $a := . -}}
+			{{ printf "%#v: %s\n" $a (ternary "HUGE" "not so huge" $a) }}
+		{{- end -}}
+	`)
+
+	err = tmpl.Execute(os.Stdout, []interface{}{
+		true,
+		false,
+		"true",
+		"false",
+		"",
+		nil,
+		1,
+		0,
+		9001,
+		[]bool{},
+		[]bool{false},
+	})
+	if err != nil {
+		panic(err)
+	}
+
+	// Output:
+	// true: HUGE
+	// false: not so huge
+	// "true": HUGE
+	// "false": HUGE
+	// "": not so huge
+	// <nil>: not so huge
+	// 1: HUGE
+	// 0: not so huge
+	// 9001: HUGE
+	// []bool{}: not so huge
+	// []bool{false}: HUGE
+}
+
+func Example_firstLast() {
+	tmpl, err := template.New("first-and-last").Funcs(templatelib.FuncMap).Parse(`First: {{ . | first }}, Last: {{ . | last }}`)
+
+	err = tmpl.Execute(os.Stdout, []interface{}{
+		"a",
+		"b",
+		"c",
+	})
+	if err != nil {
+		panic(err)
+	}
+
+	// Output:
+	// First: a, Last: c
+}
+
+func Example_json() {
+	tmpl, err := template.New("json").Funcs(templatelib.FuncMap).Parse(`
+		{{- json . -}}
+	`)
+
+	err = tmpl.Execute(os.Stdout, map[string]interface{}{
+		"a": []string{"1", "2", "3"},
+		"b": map[string]bool{"1": true, "2": false, "3": true},
+		"c": nil,
+	})
+	if err != nil {
+		panic(err)
+	}
+
+	// Output:
+	// {"a":["1","2","3"],"b":{"1":true,"2":false,"3":true},"c":null}
+}
+
+func Example_join() {
+	tmpl, err := template.New("join").Funcs(templatelib.FuncMap).Parse(`
+		Array: {{ . | join ", " }}{{ "\n" -}}
+		Args: {{ join ", " "a" "b" "c" -}}
+	`)
+
+	err = tmpl.Execute(os.Stdout, []string{
+		"1",
+		"2",
+		"3",
+	})
+	if err != nil {
+		panic(err)
+	}
+
+	// Output:
+	// Array: 1, 2, 3
+	// Args: a, b, c
+}
+
+func Example_trimReplaceGitToHttps() {
+	tmpl, err := template.New("git-to-https").Funcs(templatelib.FuncMap).Parse(`
+		{{- range . -}}
+			{{- . | replace "git://" "https://" | trimSuffixes ".git" }}{{ "\n" -}}
+		{{- end -}}
+	`)
+
+	err = tmpl.Execute(os.Stdout, []string{
+		"git://github.com/jsmith/some-repo.git",
+		"https://github.com/jsmith/some-repo.git",
+		"https://github.com/jsmith/some-repo",
+	})
+	if err != nil {
+		panic(err)
+	}
+
+	// Output:
+	// https://github.com/jsmith/some-repo
+	// https://github.com/jsmith/some-repo
+	// https://github.com/jsmith/some-repo
+}
+
+func Example_trimReplaceGitToGo() {
+	tmpl, err := template.New("git-to-go").Funcs(templatelib.FuncMap).Parse(`
+		{{- range . -}}
+			{{- . | trimPrefixes "git://" "http://" "https://" "ssh://" | trimSuffixes ".git" }}{{ "\n" -}}
+		{{- end -}}
+	`)
+
+	err = tmpl.Execute(os.Stdout, []string{
+		"git://github.com/jsmith/some-repo.git",
+		"https://github.com/jsmith/some-repo.git",
+		"https://github.com/jsmith/some-repo",
+		"ssh://github.com/jsmith/some-repo.git",
+		"github.com/jsmith/some-repo",
+	})
+	if err != nil {
+		panic(err)
+	}
+
+	// Output:
+	// github.com/jsmith/some-repo
+	// github.com/jsmith/some-repo
+	// github.com/jsmith/some-repo
+	// github.com/jsmith/some-repo
+	// github.com/jsmith/some-repo
+}

+ 45 - 0
bashbrew/go/vendor/src/github.com/docker-library/go-dockerlibrary/pkg/templatelib/lib_test.go

@@ -0,0 +1,45 @@
+package templatelib_test
+
+import (
+	"testing"
+	"text/template"
+	"unsafe"
+
+	"github.com/docker-library/go-dockerlibrary/pkg/templatelib"
+)
+
+func TestTernaryPanic(t *testing.T) {
+	// one of the only places template.IsTrue will return "false" for the "ok" value is an UnsafePointer (hence this test)
+
+	defer func() {
+		if r := recover(); r == nil {
+			t.Errorf("Expected panic, executed successfully instead")
+		} else if errText, ok := r.(string); !ok || errText != `template.IsTrue(<nil>) says things are NOT OK` {
+			t.Errorf("Unexpected panic: %v", errText)
+		}
+	}()
+
+	tmpl, err := template.New("unsafe-pointer").Funcs(templatelib.FuncMap).Parse(`{{ ternary "true" "false" . }}`)
+
+	err = tmpl.Execute(nil, unsafe.Pointer(uintptr(0)))
+	if err != nil {
+		t.Errorf("Expected panic, got error instead: %v", err)
+	}
+}
+
+func TestJoinPanic(t *testing.T) {
+	defer func() {
+		if r := recover(); r == nil {
+			t.Errorf("Expected panic, executed successfully instead")
+		} else if errText, ok := r.(string); !ok || errText != `"join" requires at least one argument` {
+			t.Errorf("Unexpected panic: %v", r)
+		}
+	}()
+
+	tmpl, err := template.New("join-no-arg").Funcs(templatelib.FuncMap).Parse(`{{ join }}`)
+
+	err = tmpl.Execute(nil, nil)
+	if err != nil {
+		t.Errorf("Expected panic, got error instead: %v", err)
+	}
+}