refactor how the domain for the images is used

Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
Jess Frazelle 2018-06-17 15:41:58 -04:00
parent 246604b921
commit f3a9b00ec8
No known key found for this signature in database
GPG key ID: 18F3685C0022BFF3
20 changed files with 337 additions and 149 deletions

View file

@ -64,7 +64,6 @@ GLOBAL OPTIONS:
--force-non-ssl, -f force allow use of non-ssl --force-non-ssl, -f force allow use of non-ssl
--username value, -u value username for the registry --username value, -u value username for the registry
--password value, -p value password for the registry --password value, -p value password for the registry
--registry value, -r value URL to the private registry (ex. r.j3ss.co) (default: "https://registry-1.docker.io") [$REG_REGISTRY]
--timeout value timeout for HTTP requests (default: "1m") --timeout value timeout for HTTP requests (default: "1m")
--skip-ping skip pinging the registry while establishing connection --skip-ping skip pinging the registry while establishing connection
--help, -h show help --help, -h show help
@ -87,7 +86,7 @@ not present, you can pass through flags directly.
```console ```console
# this command might take a while if you have hundreds of images like I do # this command might take a while if you have hundreds of images like I do
$ reg -r r.j3ss.co ls $ reg ls r.j3ss.co
Repositories for r.j3ss.co Repositories for r.j3ss.co
REPO TAGS REPO TAGS
awscli latest awscli latest
@ -100,7 +99,7 @@ chrome beta, latest, stable
**Tags** **Tags**
```console ```console
$ reg tags tor-browser $ reg tags r.j3ss.co/tor-browser
alpha alpha
hardened hardened
latest latest
@ -110,7 +109,7 @@ stable
### Get a Manifest ### Get a Manifest
```console ```console
$ reg manifest htop $ reg manifest r.j3ss.co/htop
{ {
"schemaVersion": 1, "schemaVersion": 1,
"name": "htop", "name": "htop",
@ -130,30 +129,30 @@ $ reg manifest htop
### Get the Digest ### Get the Digest
```console ```console
$ reg digest htop $ reg digest r.j3ss.co/htop
sha256:791158756cc0f5b27ef8c5c546284568fc9b7f4cf1429fb736aff3ee2d2e340f sha256:791158756cc0f5b27ef8c5c546284568fc9b7f4cf1429fb736aff3ee2d2e340f
``` ```
### Download a Layer ### Download a Layer
```console ```console
$ reg layer -o chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4 $ reg layer -o r.j3ss.co/chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
OR OR
$ reg layer chrome@sha256:a3ed95caeb0.. > layer.tar $ reg layer r.j3ss.co/chrome@sha256:a3ed95caeb0.. > layer.tar
``` ```
### Delete an Image ### Delete an Image
```console ```console
$ reg rm chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4 $ reg rm r.j3ss.co/chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
Deleted chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4 Deleted chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
``` ```
### Vulnerability Reports ### Vulnerability Reports
```console ```console
$ reg vulns --clair https://clair.j3ss.co chrome $ reg vulns --clair https://clair.j3ss.co r.j3ss.co/chrome
Found 32 vulnerabilities Found 32 vulnerabilities
CVE-2015-5180: [Low] CVE-2015-5180: [Low]

View file

@ -3,7 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/genuinetools/reg/repoutils" "github.com/genuinetools/reg/registry"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -16,15 +16,32 @@ var deleteCommand = cli.Command{
return fmt.Errorf("pass the name of the repository") return fmt.Errorf("pass the name of the repository")
} }
repo, ref, err := repoutils.GetRepoAndRef(c.Args()[0]) image, err := registry.ParseImage(c.Args().First())
if err != nil { if err != nil {
return err return err
} }
if err := r.Delete(repo, ref); err != nil { // Create the registry client.
return fmt.Errorf("Delete %s@%s failed: %v", repo, ref, err) r, err := createRegistryClient(c, image.Domain)
if err != nil {
return err
} }
fmt.Printf("Deleted %s@%s\n", repo, ref)
// Get the digest.
digest, err := r.Digest(image)
if err != nil {
return err
}
if err := image.WithDigest(digest); err != nil {
return err
}
// Delete the reference.
if err := r.Delete(image.Path, digest); err != nil {
return err
}
fmt.Printf("Deleted %s\n", image.String())
return nil return nil
}, },

View file

@ -1,37 +1,27 @@
package main package main
import ( import (
"fmt"
"strings" "strings"
"testing" "testing"
) )
func TestDelete(t *testing.T) { func TestDelete(t *testing.T) {
// Make sure we have busybox in list. // Make sure we have busybox in list.
out, err := run("ls") out, err := run("ls", domain)
if err != nil { if err != nil {
t.Fatalf("output: %s, error: %v", string(out), err) t.Fatalf("output: %s, error: %v", out, err)
} }
expected := []string{"alpine latest", "busybox glibc, musl, latest"} expected := `REPO TAGS
for _, e := range expected { alpine 3.5, latest
if !strings.Contains(out, e) { busybox glibc, latest, musl`
t.Logf("expected to contain: %s\ngot: %s", e, out) if !strings.HasSuffix(strings.TrimSpace(out), expected) {
} t.Fatalf("expected to contain: %s\ngot: %s", expected, out)
} }
// Remove busybox image. // Remove busybox image.
if out, err := run("rm", "busybox"); err != nil { if out, err := run("rm", fmt.Sprintf("%s/busybox:glibc", domain)); err != nil {
t.Fatalf("output: %s, error: %v", string(out), err) t.Fatalf("output: %s, error: %v", out, err)
} }
// Make sure there is no busybox in list.
out, err = run("ls")
if err != nil {
t.Fatalf("output: %s, error: %v", string(out), err)
}
expected = []string{"alpine latest", "busybox glibc, musl\n"}
for _, e := range expected {
if !strings.Contains(out, e) {
t.Logf("expected to contain: %s\ngot: %s", e, out)
}
}
} }

View file

@ -3,7 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/genuinetools/reg/repoutils" "github.com/genuinetools/reg/registry"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -15,17 +15,24 @@ var digestCommand = cli.Command{
return fmt.Errorf("pass the name of the repository") return fmt.Errorf("pass the name of the repository")
} }
repo, ref, err := repoutils.GetRepoAndRef(c.Args()[0]) image, err := registry.ParseImage(c.Args().First())
if err != nil { if err != nil {
return err return err
} }
digest, err := r.Digest(repo, ref) // Create the registry client.
r, err := createRegistryClient(c, image.Domain)
if err != nil { if err != nil {
return err return err
} }
fmt.Println(digest) // Get the digest.
digest, err := r.Digest(image)
if err != nil {
return err
}
fmt.Println(digest.String())
return nil return nil
}, },

View file

@ -5,8 +5,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/genuinetools/reg/repoutils" "github.com/genuinetools/reg/registry"
digest "github.com/opencontainers/go-digest"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -25,12 +24,25 @@ var layerCommand = cli.Command{
return fmt.Errorf("pass the name of the repository") return fmt.Errorf("pass the name of the repository")
} }
repo, ref, err := repoutils.GetRepoAndRef(c.Args()[0]) image, err := registry.ParseImage(c.Args().First())
if err != nil { if err != nil {
return err return err
} }
layer, err := r.DownloadLayer(repo, digest.FromString(ref)) // Create the registry client.
r, err := createRegistryClient(c, image.Domain)
if err != nil {
return err
}
// Get the digest.
digest, err := r.Digest(image)
if err != nil {
return err
}
// Download the layer.
layer, err := r.DownloadLayer(image.Path, digest)
if err != nil { if err != nil {
return err return err
} }

33
layer_test.go Normal file
View file

@ -0,0 +1,33 @@
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)
func TestLayer(t *testing.T) {
// Get the digest.
out, err := run("digest", fmt.Sprintf("%s/busybox", domain))
if err != nil {
t.Fatalf("output: %s, error: %v", out, err)
}
tmpf := filepath.Join(os.TempDir(), "download-layer.tar")
defer os.RemoveAll(tmpf)
// Download the layer.
lines := strings.Split(strings.TrimSpace(out), "\n")
layer := fmt.Sprintf("%s/busybox@%s", domain, strings.TrimSpace(lines[len(lines)-1]))
out, err = run("layer", "-o", tmpf, layer)
if err != nil {
t.Fatalf("output: %s, error: %v", out, err)
}
// Make sure the file exists
if _, err := os.Stat(tmpf); os.IsNotExist(err) {
t.Fatalf("%s should exist after downloading the layer but it didn't", tmpf)
}
}

61
list.go
View file

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"sort"
"strings" "strings"
"sync" "sync"
"text/tabwriter" "text/tabwriter"
@ -15,13 +16,46 @@ var listCommand = cli.Command{
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "list all repositories", Usage: "list all repositories",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if len(c.Args()) < 1 {
return fmt.Errorf("pass the domain of the registry")
}
// Create the registry client.
r, err := createRegistryClient(c, c.Args().First())
if err != nil {
return err
}
// Get the repositories via catalog. // Get the repositories via catalog.
repos, err := r.Catalog("") repos, err := r.Catalog("")
if err != nil { if err != nil {
return err return err
} }
sort.Strings(repos)
fmt.Printf("Repositories for %s\n", auth.ServerAddress) fmt.Printf("Repositories for %s\n", r.Domain)
var (
wg sync.WaitGroup
repoTags = map[string][]string{}
)
wg.Add(len(repos))
for _, repo := range repos {
go func(repo string) {
// Get the tags.
tags, err := r.Tags(repo)
if err != nil {
fmt.Printf("Get tags of [%s] error: %s", repo, err)
}
// Sort the tags
sort.Strings(tags)
repoTags[repo] = tags
wg.Done()
}(repo)
}
wg.Wait()
// Setup the tab writer. // Setup the tab writer.
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
@ -29,31 +63,10 @@ var listCommand = cli.Command{
// Print header. // Print header.
fmt.Fprintln(w, "REPO\tTAGS") fmt.Fprintln(w, "REPO\tTAGS")
var ( // Sort the repos.
l sync.Mutex
wg sync.WaitGroup
)
wg.Add(len(repos))
for _, repo := range repos { for _, repo := range repos {
go func(repo string) { w.Write([]byte(fmt.Sprintf("%s\t%s\n", repo, strings.Join(repoTags[repo], ", "))))
// Get the tags and print to stdout.
tags, err := r.Tags(repo)
if err != nil {
fmt.Printf("Get tags of [%s] error: %s", repo, err)
}
out := fmt.Sprintf("%s\t%s\n", repo, strings.Join(tags, ", "))
// Lock around the tabwriter to prevent garbled output.
// See: https://github.com/genuinetools/reg/issues/54
l.Lock()
w.Write([]byte(out))
l.Unlock()
wg.Done()
}(repo)
} }
wg.Wait()
w.Flush() w.Flush()

View file

@ -6,14 +6,15 @@ import (
) )
func TestList(t *testing.T) { func TestList(t *testing.T) {
out, err := run("ls") out, err := run("ls", domain)
if err != nil { if err != nil {
t.Fatalf("output: %s, error: %v", string(out), err) t.Fatalf("output: %s, error: %v", out, err)
} }
expected := []string{"alpine latest", "busybox glibc, musl"}
for _, e := range expected { expected := `REPO TAGS
if !strings.Contains(out, e) { alpine 3.5, latest
t.Logf("expected to contain: %s\ngot: %s", e, out) busybox glibc, latest, musl`
} if !strings.HasSuffix(strings.TrimSpace(out), expected) {
t.Fatalf("expected to contain: %s\ngot: %s", expected, out)
} }
} }

63
main.go
View file

@ -6,7 +6,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/docker/docker/api/types"
"github.com/genuinetools/reg/registry" "github.com/genuinetools/reg/registry"
"github.com/genuinetools/reg/repoutils" "github.com/genuinetools/reg/repoutils"
"github.com/genuinetools/reg/version" "github.com/genuinetools/reg/version"
@ -14,11 +13,6 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
var (
auth types.AuthConfig
r *registry.Registry
)
func main() { func main() {
app := cli.NewApp() app := cli.NewApp()
app.Name = "reg" app.Name = "reg"
@ -48,12 +42,6 @@ func main() {
Name: "password, p", Name: "password, p",
Usage: "password for the registry", Usage: "password for the registry",
}, },
cli.StringFlag{
Name: "registry, r",
Usage: "URL to the private registry (ex. r.j3ss.co)",
Value: repoutils.DefaultDockerRegistry,
EnvVar: "REG_REGISTRY",
},
cli.StringFlag{ cli.StringFlag{
Name: "timeout", Name: "timeout",
Value: "1m", Value: "1m",
@ -90,33 +78,36 @@ func main() {
return return
} }
auth, err = repoutils.GetAuthConfig(c.GlobalString("username"), c.GlobalString("password"), c.GlobalString("registry")) return
if err != nil {
return err
}
// Prevent non-ssl unless explicitly forced
if !c.GlobalBool("force-non-ssl") && strings.HasPrefix(auth.ServerAddress, "http:") {
return fmt.Errorf("Attempt to use insecure protocol! Use non-ssl option to force")
}
// Parse the timeout.
timeout, err := time.ParseDuration(c.GlobalString("timeout"))
if err != nil {
return fmt.Errorf("parsing %s as duration failed: %v", c.GlobalString("timeout"), err)
}
// Create the registry client.
r, err = registry.New(auth, registry.Opt{
Insecure: c.GlobalBool("insecure"),
Debug: c.GlobalBool("debug"),
SkipPing: c.GlobalBool("skip-ping"),
Timeout: timeout,
})
return err
} }
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
} }
func createRegistryClient(c *cli.Context, domain string) (*registry.Registry, error) {
auth, err := repoutils.GetAuthConfig(c.GlobalString("username"), c.GlobalString("password"), domain)
if err != nil {
return nil, err
}
// Prevent non-ssl unless explicitly forced
if !c.GlobalBool("force-non-ssl") && strings.HasPrefix(auth.ServerAddress, "http:") {
return nil, fmt.Errorf("Attempt to use insecure protocol! Use non-ssl option to force")
}
// Parse the timeout.
timeout, err := time.ParseDuration(c.GlobalString("timeout"))
if err != nil {
return nil, fmt.Errorf("parsing %s as duration failed: %v", c.GlobalString("timeout"), err)
}
// Create the registry client.
return registry.New(auth, registry.Opt{
Insecure: c.GlobalBool("insecure"),
Debug: c.GlobalBool("debug"),
SkipPing: c.GlobalBool("skip-ping"),
Timeout: timeout,
})
}

View file

@ -13,6 +13,10 @@ import (
"github.com/genuinetools/reg/testutils" "github.com/genuinetools/reg/testutils"
) )
const (
domain = "localhost:5000"
)
var ( var (
exeSuffix string // ".exe" on Windows exeSuffix string // ".exe" on Windows
@ -103,8 +107,9 @@ func TestMain(m *testing.M) {
func run(args ...string) (string, error) { func run(args ...string) (string, error) {
prog := "./testreg" + exeSuffix prog := "./testreg" + exeSuffix
// always add trust insecure, and the registry // always add trust insecure, and the registry
newargs := append([]string{"-d", "-k", "-r", "localhost:5000"}, args...) newargs := append([]string{"-d", "-k"}, args...)
cmd := exec.Command(prog, newargs...) cmd := exec.Command(prog, newargs...)
cmd.Env = []string{"REG_REGISTRY=localhost:5000"}
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
return string(out), err return string(out), err
} }

View file

@ -4,7 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/genuinetools/reg/repoutils" "github.com/genuinetools/reg/registry"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -22,7 +22,13 @@ var manifestCommand = cli.Command{
return fmt.Errorf("pass the name of the repository") return fmt.Errorf("pass the name of the repository")
} }
repo, ref, err := repoutils.GetRepoAndRef(c.Args()[0]) image, err := registry.ParseImage(c.Args().First())
if err != nil {
return err
}
// Create the registry client.
r, err := createRegistryClient(c, image.Domain)
if err != nil { if err != nil {
return err return err
} }
@ -30,13 +36,13 @@ var manifestCommand = cli.Command{
var manifest interface{} var manifest interface{}
if c.Bool("v1") { if c.Bool("v1") {
// Get the v1 manifest if it was explicitly asked for. // Get the v1 manifest if it was explicitly asked for.
manifest, err = r.ManifestV1(repo, ref) manifest, err = r.ManifestV1(image.Path, image.Reference())
if err != nil { if err != nil {
return err return err
} }
} else { } else {
// Get the v2 manifest. // Get the v2 manifest.
manifest, err = r.Manifest(repo, ref) manifest, err = r.Manifest(image.Path, image.Reference())
if err != nil { if err != nil {
return err return err
} }

31
manifest_test.go Normal file
View file

@ -0,0 +1,31 @@
package main
import (
"fmt"
"strings"
"testing"
)
func TestManifestV2(t *testing.T) {
out, err := run("manifest", fmt.Sprintf("%s/busybox", domain))
if err != nil {
t.Fatalf("output: %s, error: %v", out, err)
}
expected := `"schemaVersion": 2,`
if !strings.Contains(out, expected) {
t.Fatalf("expected: %s\ngot: %s", expected, out)
}
}
func TestManifestV1(t *testing.T) {
out, err := run("manifest", "--v1", fmt.Sprintf("%s/busybox", domain))
if err != nil {
t.Fatalf("output: %s, error: %v", out, err)
}
expected := `"schemaVersion": 1,`
if !strings.Contains(out, expected) {
t.Fatalf("expected: %s\ngot: %s", expected, out)
}
}

View file

@ -5,24 +5,12 @@ import (
"net/http" "net/http"
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
ocd "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
) )
// Delete removes a repository digest or reference from the registry. // Delete removes a repository digest from the registry.
// https://docs.docker.com/registry/spec/api/#deleting-an-image // https://docs.docker.com/registry/spec/api/#deleting-an-image
func (r *Registry) Delete(repository, digest string) error { func (r *Registry) Delete(repository string, digest digest.Digest) (err error) {
// If digest is not valid try resolving it as a reference
if _, err := ocd.Parse(digest); err != nil {
digest, err = r.Digest(repository, digest)
if err != nil {
return err
}
if digest == "" {
return nil
}
}
// Delete the image.
url := r.url("/v2/%s/manifests/%s", repository, digest) url := r.url("/v2/%s/manifests/%s", repository, digest)
r.Logf("registry.manifests.delete url=%s repository=%s digest=%s", r.Logf("registry.manifests.delete url=%s repository=%s digest=%s",
url, repository, digest) url, repository, digest)

View file

@ -5,20 +5,26 @@ import (
"net/http" "net/http"
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
digest "github.com/opencontainers/go-digest"
) )
// Digest returns the digest for a repository and reference. // Digest returns the digest for an image.
func (r *Registry) Digest(repository, ref string) (string, error) { func (r *Registry) Digest(image Image) (digest.Digest, error) {
url := r.url("/v2/%s/manifests/%s", repository, ref) if len(image.Digest) > 1 {
// return early if we already have an image digest.
return image.Digest, nil
}
url := r.url("/v2/%s/manifests/%s", image.Path, image.Tag)
r.Logf("registry.manifests.get url=%s repository=%s ref=%s", r.Logf("registry.manifests.get url=%s repository=%s ref=%s",
url, repository, ref) url, image.Path, image.Tag)
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
if err != nil { if err != nil {
return "", err return "", err
} }
req.Header.Set("Accept", schema2.MediaTypeManifest)
req.Header.Set("Accept", schema2.MediaTypeManifest)
resp, err := r.Client.Do(req) resp, err := r.Client.Do(req)
if err != nil { if err != nil {
return "", err return "", err
@ -29,6 +35,5 @@ func (r *Registry) Digest(repository, ref string) (string, error) {
return "", fmt.Errorf("Got status code: %d", resp.StatusCode) return "", fmt.Errorf("Got status code: %d", resp.StatusCode)
} }
digest := resp.Header.Get("Docker-Content-Digest") return digest.FromString(resp.Header.Get("Docker-Content-Digest")), nil
return digest, nil
} }

67
registry/image.go Normal file
View file

@ -0,0 +1,67 @@
package registry
import (
"fmt"
"github.com/docker/distribution/reference"
digest "github.com/opencontainers/go-digest"
)
// Image holds information about an image.
type Image struct {
Domain string
Path string
Tag string
Digest digest.Digest
named reference.Named
}
// String returns the string representation of an image.
func (i Image) String() string {
return i.named.String()
}
// Reference returns either the digest if it is non-empty or the tag for the image.
func (i Image) Reference() string {
if len(i.Digest.String()) > 1 {
return i.Digest.String()
}
return i.Tag
}
// WithDigest sets the digest for an image.
func (i *Image) WithDigest(digest digest.Digest) (err error) {
i.Digest = digest
i.named, err = reference.WithDigest(i.named, digest)
return err
}
// ParseImage returns an Image struct with all the values filled in for a given image.
func ParseImage(image string) (Image, error) {
// Parse the image name and tag.
named, err := reference.ParseNormalizedNamed(image)
if err != nil {
return Image{}, fmt.Errorf("parsing image %q failed: %v", image, err)
}
// Add the latest lag if they did not provide one.
named = reference.TagNameOnly(named)
i := Image{
named: named,
Domain: reference.Domain(named),
Path: reference.Path(named),
}
// Add the tag if there was one.
if tagged, ok := named.(reference.Tagged); ok {
i.Tag = tagged.Tag()
}
// Add the digest if there was one.
if canonical, ok := named.(reference.Canonical); ok {
i.Digest = canonical.Digest()
}
return i, nil
}

View file

@ -87,17 +87,17 @@ func (r *Registry) ManifestV1(repository, ref string) (schema1.SignedManifest, e
return m, nil return m, nil
} }
// PutManifest calls a PUT for the specific manifest for an image.
func (r *Registry) PutManifest(repository, ref string, manifest distribution.Manifest) error { func (r *Registry) PutManifest(repository, ref string, manifest distribution.Manifest) error {
url := r.url("/v2/%s/manifests/%s", repository, ref) url := r.url("/v2/%s/manifests/%s", repository, ref)
r.Logf("registry.manifest.put url=%s repository=%s reference=%s", url, repository, ref) r.Logf("registry.manifest.put url=%s repository=%s reference=%s", url, repository, ref)
manifestJson, err := json.Marshal(manifest) b, err := json.Marshal(manifest)
if err != nil { if err != nil {
return err return err
} }
buffer := bytes.NewBuffer(manifestJson) req, err := http.NewRequest("PUT", url, bytes.NewBuffer(b))
req, err := http.NewRequest("PUT", url, buffer)
if err != nil { if err != nil {
return err return err
} }

16
tags.go
View file

@ -2,8 +2,10 @@ package main
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"github.com/genuinetools/reg/registry"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -15,11 +17,23 @@ var tagsCommand = cli.Command{
return fmt.Errorf("pass the name of the repository") return fmt.Errorf("pass the name of the repository")
} }
tags, err := r.Tags(c.Args()[0]) image, err := registry.ParseImage(c.Args().First())
if err != nil { if err != nil {
return err return err
} }
// Create the registry client.
r, err := createRegistryClient(c, image.Domain)
if err != nil {
return err
}
tags, err := r.Tags(image.Path)
if err != nil {
return err
}
sort.Strings(tags)
// Print the tags. // Print the tags.
fmt.Println(strings.Join(tags, "\n")) fmt.Println(strings.Join(tags, "\n"))

View file

@ -1,19 +1,21 @@
package main package main
import ( import (
"fmt"
"strings" "strings"
"testing" "testing"
) )
func TestTags(t *testing.T) { func TestTags(t *testing.T) {
out, err := run("tags", "busybox") out, err := run("tags", fmt.Sprintf("%s/busybox", domain))
if err != nil { if err != nil {
t.Fatalf("output: %s, error: %v", string(out), err) t.Fatalf("output: %s, error: %v", out, err)
} }
expected := `glibc expected := `glibc
latest
musl musl
` `
if !strings.HasSuffix(out, expected) { if !strings.HasSuffix(out, expected) {
t.Logf("expected: %s\ngot: %s", expected, out) t.Fatalf("expected: %s\ngot: %s", expected, out)
} }
} }

View file

@ -6,7 +6,7 @@ import (
"time" "time"
"github.com/genuinetools/reg/clair" "github.com/genuinetools/reg/clair"
"github.com/genuinetools/reg/repoutils" "github.com/genuinetools/reg/registry"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -38,7 +38,13 @@ var vulnsCommand = cli.Command{
return fmt.Errorf("pass the name of the repository") return fmt.Errorf("pass the name of the repository")
} }
repo, ref, err := repoutils.GetRepoAndRef(c.Args()[0]) image, err := registry.ParseImage(c.Args().First())
if err != nil {
return err
}
// Create the registry client.
r, err := createRegistryClient(c, image.Domain)
if err != nil { if err != nil {
return err return err
} }
@ -60,7 +66,7 @@ var vulnsCommand = cli.Command{
} }
// Get the vulnerability report. // Get the vulnerability report.
report, err := cr.Vulnerabilities(r, repo, ref) report, err := cr.Vulnerabilities(r, image.Path, image.Reference())
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,18 +1,19 @@
package main package main
import ( import (
"fmt"
"strings" "strings"
"testing" "testing"
) )
func TestVulns(t *testing.T) { func TestVulns(t *testing.T) {
out, err := run("vulns", "--clair", "http://localhost:6060", "alpine:3.5") out, err := run("vulns", "--clair", "http://localhost:6060", fmt.Sprintf("%s/alpine:3.5", domain))
if err != nil { if err != nil {
t.Fatalf("output: %s, error: %v", string(out), err) t.Fatalf("output: %s, error: %v", out, err)
} }
expected := `clair.clair resp.Status=200 OK` expected := `clair.clair resp.Status=200 OK`
if !strings.HasSuffix(strings.TrimSpace(out), expected) { if !strings.HasSuffix(strings.TrimSpace(out), expected) {
t.Logf("expected: %s\ngot: %s", expected, out) t.Fatalf("expected: %s\ngot: %s", expected, out)
} }
} }