Skip to content

Commit

Permalink
Refactor manifest-tool, add a subcommand to push that makes this util…
Browse files Browse the repository at this point in the history
… easy to use from the command line directly without creating a specific file. Also fix some tests and cleanup
  • Loading branch information
luxas committed Jan 3, 2017
1 parent b4a9ba1 commit 08f1af2
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 148 deletions.
53 changes: 13 additions & 40 deletions docker/createml.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,27 @@ package docker
import (
"bytes"
"fmt"
"io/ioutil"

"net"
"net/http"
"net/url"
"path/filepath"

"strings"
"time"

"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/registry/api/v2"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/transport"
"github.com/go-yaml/yaml"

"github.com/docker/docker/dockerversion"
"github.com/docker/docker/reference"
"github.com/docker/docker/registry"
)

// YAMLInput represents the YAML format input to the pushml
// command.
type YAMLInput struct {
Image string
Manifests []ManifestEntry
}

// ManifestEntry represents an entry in the list of manifests to
// be combined into a manifest list, provided via the YAML input
type ManifestEntry struct {
Image string
Platform manifestlist.PlatformSpec
}
"github.com/estesp/manifest-tool/types"
)

// we will store up a list of blobs we must ask the registry
// to cross-mount into our target namespace
Expand All @@ -53,31 +39,18 @@ type blobMount struct {
type manifestPush struct {
Name string
Digest string
JsonBytes []byte
JSONBytes []byte
MediaType string
}

func PutManifestList(c *cli.Context, filePath string) (string, error) {
// PutManifestList takes an authentication variable and a yaml spec struct and pushes an image list based on the spec
func PutManifestList(a *types.AuthInfo, yamlInput types.YAMLInput) (string, error) {
var (
yamlInput YAMLInput
manifestList manifestlist.ManifestList
blobMountRequests []blobMount
manifestRequests []manifestPush
)

filename, err := filepath.Abs(filePath)
if err != nil {
return "", fmt.Errorf("Can't resolve path to %q: %v", filePath, err)
}
yamlFile, err := ioutil.ReadFile(filename)
if err != nil {
return "", fmt.Errorf("Can't read YAML file %q: %v", filePath, err)
}
err = yaml.Unmarshal(yamlFile, &yamlInput)
if err != nil {
return "", fmt.Errorf("Can't unmarshal YAML file %q: %v", filePath, err)
}

// process the final image name reference for the manifest list
targetRef, err := reference.ParseNamed(yamlInput.Image)
if err != nil {
Expand All @@ -100,7 +73,7 @@ func PutManifestList(c *cli.Context, filePath string) (string, error) {
if !isValidOSArch(img.Platform.OS, img.Platform.Architecture) {
return "", fmt.Errorf("Manifest entry for image %s has unsupported os/arch combination: %s/%s", img.Image, img.Platform.OS, img.Platform.Architecture)
}
mfstData, repoInfo, err := GetImageData(c, img.Image)
mfstData, repoInfo, err := GetImageData(a, img.Image)
if err != nil {
return "", fmt.Errorf("Inspect of image %q failed with error: %v", img.Image, err)
}
Expand Down Expand Up @@ -138,7 +111,7 @@ func PutManifestList(c *cli.Context, filePath string) (string, error) {
manifestRequests = append(manifestRequests, manifestPush{
Name: repoInfo.RemoteName(),
Digest: imgMfst.Digest,
JsonBytes: imgMfst.CanonicalJson,
JSONBytes: imgMfst.CanonicalJSON,
MediaType: imgMfst.MediaType,
})
}
Expand Down Expand Up @@ -174,7 +147,7 @@ func PutManifestList(c *cli.Context, filePath string) (string, error) {
}
putRequest.Header.Set("Content-Type", mediaType)

httpClient, err := getHTTPClient(c, targetRepo, targetEndpoint, repoName)
httpClient, err := getHTTPClient(a, targetRepo, targetEndpoint, repoName)
if err != nil {
return "", fmt.Errorf("Failed to setup HTTP client to repository: %v", err)
}
Expand Down Expand Up @@ -209,7 +182,7 @@ func PutManifestList(c *cli.Context, filePath string) (string, error) {
return "", fmt.Errorf("Registry push unsuccessful: response %d: %s", resp.StatusCode, resp.Status)
}

func getHTTPClient(c *cli.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, repoName string) (*http.Client, error) {
func getHTTPClient(a *types.AuthInfo, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, repoName string) (*http.Client, error) {
// get the http transport, this will be used in a client to upload manifest
// TODO - add separate function get client
base := &http.Transport{
Expand All @@ -223,7 +196,7 @@ func getHTTPClient(c *cli.Context, repoInfo *registry.RepositoryInfo, endpoint r
TLSClientConfig: endpoint.TLSConfig,
DisableKeepAlives: true,
}
authConfig, err := getAuthConfig(c, repoInfo.Index)
authConfig, err := getAuthConfig(a, repoInfo.Index)
if err != nil {
return nil, fmt.Errorf("Cannot retrieve authconfig: %v", err)
}
Expand Down Expand Up @@ -317,7 +290,7 @@ func pushReferences(httpClient *http.Client, urlBuilder *v2.URLBuilder, ref refe
}
logrus.Debugf("manifest reference push URL: %s", pushURL)

pushRequest, err := http.NewRequest("PUT", pushURL, bytes.NewReader(manifest.JsonBytes))
pushRequest, err := http.NewRequest("PUT", pushURL, bytes.NewReader(manifest.JSONBytes))
if err != nil {
return fmt.Errorf("HTTP PUT request creation for manifest reference push failed: %v", err)
}
Expand Down
65 changes: 32 additions & 33 deletions docker/createml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,39 @@ package docker

import "testing"

func TeststatusSuccess(t *testing.T) {
var crctstatus = []struct {
a int
}{
{200},
{239},
{278},
{300},
{399},
}
var wrngstatus = []struct {
b int
}{
{1},
{50},
{111},
{199},
{400},
{1000},
}
func TestStatusSuccess(t *testing.T) {
var crctstatus = []struct {
a int
}{
{200},
{239},
{278},
{300},
{399},
}
var wrngstatus = []struct {
b int
}{
{1},
{50},
{111},
{199},
{400},
{1000},
}

for _, i := range crctstatus {
res := statusSuccess(i.a)
if res != true {
t.Errorf("%d is an invalid status", i.a)
}
for _, i := range crctstatus {
res := statusSuccess(i.a)
if res != true {
t.Errorf("%d is an invalid status", i.a)
}

for _, j := range wrngstatus {
res := statusSuccess(j.b)
if res == true {
t.Errorf("%d is an invalid status", j.b)
}
for _, j := range wrngstatus {
res := statusSuccess(j.b)
if res == true {
t.Errorf("%d is an invalid status", j.b)
}

}
}
}
}
}

20 changes: 10 additions & 10 deletions docker/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"time"

"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
distreference "github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/api/v2"
Expand Down Expand Up @@ -151,7 +150,8 @@ func checkHTTPRedirect(req *http.Request, via []*http.Request) error {
return nil
}

func GetImageData(c *cli.Context, name string) ([]types.ImageInspect, *registry.RepositoryInfo, error) {
// GetImageData takes registry authentication information and a name of the image to return information about
func GetImageData(a *types.AuthInfo, name string) ([]types.ImageInspect, *registry.RepositoryInfo, error) {
if err := validateName(name); err != nil {
return nil, nil, err
}
Expand All @@ -163,7 +163,7 @@ func GetImageData(c *cli.Context, name string) ([]types.ImageInspect, *registry.
if err != nil {
return nil, nil, err
}
authConfig, err := getAuthConfig(c, repoInfo.Index)
authConfig, err := getAuthConfig(a, repoInfo.Index)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -285,15 +285,15 @@ func newManifestFetcher(endpoint registry.APIEndpoint, repoInfo *registry.Reposi
return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
}

func getAuthConfig(c *cli.Context, index *registryTypes.IndexInfo) (engineTypes.AuthConfig, error) {
func getAuthConfig(a *types.AuthInfo, index *registryTypes.IndexInfo) (engineTypes.AuthConfig, error) {

var (
username = c.GlobalString("username")
password = c.GlobalString("password")
cfg = c.GlobalString("docker-cfg")
username = a.Username
password = a.Password
cfg = a.DockerCfg
defAuthConfig = engineTypes.AuthConfig{
Username: c.GlobalString("username"),
Password: c.GlobalString("password"),
Username: a.Username,
Password: a.Password,
Email: "[email protected]",
}
)
Expand Down Expand Up @@ -347,7 +347,7 @@ func makeImageInspect(img *image.Image, tag string, mfInfo manifestInfo, mediaTy
Os: img.OS,
Layers: digests,
Platform: mfInfo.platform,
CanonicalJson: mfInfo.jsonBytes,
CanonicalJSON: mfInfo.jsonBytes,
}
}

Expand Down
72 changes: 34 additions & 38 deletions docker/inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package docker

import "testing"

func TestsplitHostname(t *testing.T) {
func TestSplitHostname(t *testing.T) {
var crcthostnames = []struct {
a, b, c string
}{
Expand All @@ -21,48 +21,44 @@ func TestsplitHostname(t *testing.T) {
for _, i := range crcthostnames {
res1, res2 := splitHostname(i.a)
if res1 != i.b || res2 != i.c {
t.Errorf("%s is an invalid hostname", i.a)
t.Errorf("%s should produce equals of: %q %q or %q %q", i.a, res1, i.b, res2, i.c)
}
}

for _, j := range wrnghostnames {
res1, res2 := splitHostname(i.a)
if res1 == j.e || res2 == j.f {
t.Errorf("%s is an invalid hostname", j.d)
}

for _, j := range wrnghostnames {
res1, res2 := splitHostname(j.d)
if res1 == j.e || res2 == j.f {
t.Errorf("%s should not produce equals of: %q %q or %q %q", j.d, res1, j.e, res2, j.f)
}
}
}

func Testvalidatename(t *testing.T) {
var crctnames = []struct {
a string
}{
{"localhost:5000/hello-world"},
{"myregistrydomain:5000/java"},
{"docker.io/busybox"},
}
var wrngnames = []struct {
b string
}{
{"localhost:5000,hello-world"},
{"myregistrydomain:5000&java"},
{"docker.io@busybox"},
}

for _, i := range crctnames {
res := validateName(i.a)
if res != nil{
t.Errorf("%s is an invalid name", i.a)
}

for _, j := range wrngnames {
res := validateName(j.b)
if res == nil {
t.Errorf("%s is an invalid name", j.b)
}
func TestValidateName(t *testing.T) {
var crctnames = []struct {
a string
}{
{"localhost:5000/hello-world"},
{"myregistrydomain:5000/java"},
{"docker.io/busybox"},
}
var wrngnames = []struct {
b string
}{
{"localhost:5000,hello-world"},
{"myregistrydomain:5000&java"},
{"docker.io@busybox"},
}

}
}
for _, i := range crctnames {
res := validateName(i.a)
if res != nil {
t.Errorf("%s is an invalid name", i.a)
}
}
for _, j := range wrngnames {
res := validateName(j.b)
if res == nil {
t.Errorf("%s is an invalid name", j.b)
}
}
}

3 changes: 2 additions & 1 deletion inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ var inspectCmd = cli.Command{
Action: func(c *cli.Context) {

name := c.Args().First()
imgInspect, _, err := docker.GetImageData(c, name)
a := getAuthInfo(c)
imgInspect, _, err := docker.GetImageData(a, name)
if err != nil {
logrus.Fatal(err)
}
Expand Down
6 changes: 3 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
)

const (
version = "0.2"
usage = "inspect and push manifest list images on a registry"
version = "0.3"
usage = "inspect and push manifest list images to a registry"
)

func main() {
Expand Down Expand Up @@ -48,7 +48,7 @@ func main() {
// currently support inspect and pushml
app.Commands = []cli.Command{
inspectCmd,
pushmlCmd,
pushCmd,
}

if err := app.Run(os.Args); err != nil {
Expand Down
Loading

0 comments on commit 08f1af2

Please sign in to comment.