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

Add an initial "manifest-tool" implementation #3058

Merged
merged 1 commit into from
Jun 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 80 additions & 10 deletions bashbrew/go/src/bashbrew/cmd-put-shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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())
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

See estesp/manifest-tool#23 for some discussion of making manifest-tool more efficient for our use case (and thus removing the need for so many nested loops 😇).

}

// 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, ", "))
}
}

Expand Down
10 changes: 8 additions & 2 deletions bashbrew/go/src/bashbrew/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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{} {
Expand All @@ -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": {
Expand Down
24 changes: 22 additions & 2 deletions bashbrew/go/src/bashbrew/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/codegangsta/cli"

Expand All @@ -24,6 +25,8 @@ var (
constraints []string
exclusiveConstraints bool

archNamespaces map[string]string

debugFlag = false
noSortFlag = false

Expand All @@ -35,6 +38,9 @@ var (
"library": "BASHBREW_LIBRARY",
"cache": "BASHBREW_CACHE",
"pull": "BASHBREW_PULL",

"constraint": "BASHBREW_CONSTRAINTS",
"arch-namespace": "BASHBREW_ARCH_NAMESPACES",
}
)

Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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
Expand Down
36 changes: 36 additions & 0 deletions bashbrew/go/src/bashbrew/manifest-tool.go
Original file line number Diff line number Diff line change
@@ -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()
}
1 change: 1 addition & 0 deletions bashbrew/go/src/bashbrew/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
}
}
Expand Down
2 changes: 1 addition & 1 deletion bashbrew/go/vendor/manifest
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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"},
}