Add an initial "manifest-tool" implementation

This commit is contained in:
Tianon Gravi 2017-06-13 09:33:53 -07:00
parent 9c538e056d
commit 1853c28093
7 changed files with 173 additions and 15 deletions

View File

@ -4,11 +4,50 @@ import (
"fmt"
"os"
"path"
"strings"
"github.com/codegangsta/cli"
"github.com/docker-library/go-dockerlibrary/architecture"
"github.com/docker-library/go-dockerlibrary/manifest"
)
func entriesToManifestToolYaml(r Repo, entries ...*manifest.Manifest2822Entry) (string, error) {
yaml := ""
entryIdentifiers := []string{}
for _, entry := range entries {
entryIdentifiers = append(entryIdentifiers, r.EntryIdentifier(*entry))
for _, arch := range entry.Architectures {
var ok bool
var ociArch architecture.OCIPlatform
if ociArch, ok = architecture.SupportedArches[arch]; !ok {
// skip unsupported arches
// TODO turn this into explicit validation checks at "parse" time instead (so that unsupported arches result in concrete user-facing errors long before this block of code)
continue
}
var archNamespace string
if archNamespace, ok = archNamespaces[arch]; !ok || archNamespace == "" {
fmt.Fprintf(os.Stderr, "warning: no arch-namespace specified for %q; skipping %q\n", arch, r.EntryIdentifier(*entry))
continue
}
yaml += fmt.Sprintf(" - image: %s/%s:%s\n platform:\n", archNamespace, r.RepoName, entry.Tags[0])
yaml += fmt.Sprintf(" os: %s\n", ociArch.OS)
yaml += fmt.Sprintf(" architecture: %s\n", ociArch.Architecture)
if ociArch.Variant != "" {
yaml += fmt.Sprintf(" variant: %s\n", ociArch.Variant)
}
}
}
if yaml == "" {
return "", fmt.Errorf("failed gathering images for creating %q", entryIdentifiers)
}
return "manifests:\n" + yaml, nil
}
func cmdPutShared(c *cli.Context) error {
repos, err := repos(c.Bool("all"), c.Args()...)
if err != nil {
@ -21,24 +60,55 @@ func cmdPutShared(c *cli.Context) error {
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)
// handle all multi-architecture tags first (regardless of whether they have SharedTags)
for _, entry := range r.Entries() {
// "image:" will be added later so we don't have to regenerate the entire "manifests" section every time
yaml, err := entriesToManifestToolYaml(*r, &entry)
if err != nil {
return err
}
for _, tag := range r.Tags(namespace, false, entry) {
tagYaml := fmt.Sprintf("image: %s\n%s", tag, yaml)
fmt.Printf("Putting %s\n", tag)
if err := manifestToolPushFromSpec(tagYaml); err != nil {
return fmt.Errorf("failed pushing %q (%q)", tag, entry.TagsString())
}
}
}
// TODO do something better with r.TagName (ie, the user has done something crazy like "bashbrew put-shared single-repo:single-tag")
sharedTagGroups := r.Manifest.GetSharedTagGroups()
if len(sharedTagGroups) == 0 {
continue
}
if r.TagName != "" {
fmt.Fprintf(os.Stderr, "warning: a single tag was requested -- skipping SharedTags\n")
continue
}
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])
for _, group := range sharedTagGroups {
yaml, err := entriesToManifestToolYaml(*r, group.Entries...)
if err != nil {
return err
}
for _, tag := range group.SharedTags {
tag = targetRepo + ":" + tag
tagYaml := fmt.Sprintf("image: %s\n%s", tag, yaml)
fmt.Printf("Putting shared %s\n", tag)
if err := manifestToolPushFromSpec(tagYaml); err != nil {
return fmt.Errorf("failed pushing %s", tag)
}
}
fmt.Printf("Putting %s (tags %s) <= %s\n", targetRepo, strings.Join(group.SharedTags, ", "), strings.Join(entryTags, ", "))
}
}

View File

@ -18,8 +18,6 @@ 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
@ -32,6 +30,9 @@ type FlagsConfigEntry struct {
Constraints []string `delim:"," strip:"\n\r\t "`
ExclusiveConstraints string
ApplyConstraints string
// a list of "arch=namespace" mappings for pushing indexes (manifest lists)
ArchNamespaces []string `delim:"," strip:"\n\r\t "`
}
type FlagsConfig map[string]FlagsConfigEntry
@ -70,6 +71,9 @@ func (dst *FlagsConfigEntry) Apply(src FlagsConfigEntry) {
if src.ApplyConstraints != "" {
dst.ApplyConstraints = src.ApplyConstraints
}
if len(src.ArchNamespaces) > 0 {
dst.ArchNamespaces = src.ArchNamespaces[:]
}
}
func (config FlagsConfigEntry) Vars() map[string]map[string]interface{} {
@ -82,6 +86,8 @@ func (config FlagsConfigEntry) Vars() map[string]map[string]interface{} {
"arch": config.Arch,
"constraint": config.Constraints,
"exclusive-constraints": config.ExclusiveConstraints,
"arch-namespace": config.ArchNamespaces,
},
"local": {

View File

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/codegangsta/cli"
@ -24,6 +25,8 @@ var (
constraints []string
exclusiveConstraints bool
archNamespaces map[string]string
debugFlag = false
noSortFlag = false
@ -35,6 +38,9 @@ var (
"library": "BASHBREW_LIBRARY",
"cache": "BASHBREW_CACHE",
"pull": "BASHBREW_PULL",
"constraint": "BASHBREW_CONSTRAINTS",
"arch-namespace": "BASHBREW_ARCH_NAMESPACES",
}
)
@ -84,14 +90,21 @@ func main() {
Usage: "the current platform architecture",
},
cli.StringSliceFlag{
Name: "constraint",
Usage: "build constraints (see Constraints in Manifest2822Entry)",
Name: "constraint",
EnvVar: flagEnvVars["constraint"],
Usage: "build constraints (see Constraints in Manifest2822Entry)",
},
cli.BoolFlag{
Name: "exclusive-constraints",
Usage: "skip entries which do not have Constraints",
},
cli.StringSliceFlag{
Name: "arch-namespace",
EnvVar: flagEnvVars["arch-namespace"],
Usage: `architecture to push namespace mappings for creating indexes/manifest lists ("arch=namespace" ala "s390x=tianons390x")`,
},
cli.StringFlag{
Name: "config",
Value: initDefaultConfigPath(),
@ -142,6 +155,13 @@ func main() {
constraints = c.GlobalStringSlice("constraint")
exclusiveConstraints = c.GlobalBool("exclusive-constraints")
archNamespaces = map[string]string{}
for _, archMapping := range c.GlobalStringSlice("arch-namespace") {
splitArchMapping := strings.SplitN(archMapping, "=", 2)
splitArch, splitNamespace := strings.TrimSpace(splitArchMapping[0]), strings.TrimSpace(splitArchMapping[1])
archNamespaces[splitArch] = splitNamespace
}
defaultLibrary, err = filepath.Abs(c.GlobalString("library"))
if err != nil {
return err

View File

@ -0,0 +1,36 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
)
func manifestToolPushFromSpec(yamlSpec string) error {
yamlFile, err := ioutil.TempFile("", "bashbrew-manifest-tool-yaml-")
if err != nil {
return err
}
defer os.Remove(yamlFile.Name())
if _, err := yamlFile.Write([]byte(yamlSpec)); err != nil {
return err
}
if err := yamlFile.Close(); err != nil {
return err
}
args := []string{"push", "from-spec", "--ignore-missing", yamlFile.Name()}
if debugFlag {
args = append([]string{"--debug"}, args...)
fmt.Printf("$ manifest-tool %q\n", args)
}
cmd := exec.Command("manifest-tool", args...)
cmd.Stderr = os.Stderr
if debugFlag {
cmd.Stdout = os.Stdout
}
return cmd.Run()
}

View File

@ -139,6 +139,7 @@ func (r Repo) Entries() []manifest.Manifest2822Entry {
if r.TagName == "" {
return r.Manifest.Entries
} else {
// TODO what if r.TagName isn't a single entry, but is a SharedTag ?
return []manifest.Manifest2822Entry{*r.Manifest.GetTag(r.TagName)}
}
}

2
go/vendor/manifest vendored
View File

@ -10,7 +10,7 @@
{
"importpath": "github.com/docker-library/go-dockerlibrary",
"repository": "https://github.com/docker-library/go-dockerlibrary",
"revision": "ce3ef0e05c16a5202b2c3dae35ef6a832eb18d7a",
"revision": "4fd80f3c84b66d3a62d907359f540c9417745f79",
"branch": "master"
},
{

View File

@ -0,0 +1,25 @@
package architecture
// https://github.com/opencontainers/image-spec/blob/v1.0.0-rc6/image-index.md#image-index-property-descriptions
// see "platform" (under "manifests")
type OCIPlatform struct {
OS string `json:"os"`
Architecture string `json:"architecture"`
Variant string `json:"variant,omitempty"`
//OSVersion string `json:"os.version,omitempty"`
//OSFeatures []string `json:"os.features,omitempty"`
}
var SupportedArches = map[string]OCIPlatform{
"amd64": {OS: "linux", Architecture: "amd64"},
"arm32v5": {OS: "linux", Architecture: "arm", Variant: "v5"},
"arm32v6": {OS: "linux", Architecture: "arm", Variant: "v6"},
"arm32v7": {OS: "linux", Architecture: "arm", Variant: "v7"},
"arm64v8": {OS: "linux", Architecture: "arm64", Variant: "v8"},
"i386": {OS: "linux", Architecture: "386"},
"ppc64le": {OS: "linux", Architecture: "ppc64le"},
"s390x": {OS: "linux", Architecture: "s390x"},
"windows-amd64": {OS: "windows", Architecture: "amd64"},
}