mirror of
https://github.com/genuinetools/reg.git
synced 2024-05-13 01:38:32 -04:00
refactor how the domain for the images is used
Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
parent
246604b921
commit
f3a9b00ec8
17
README.md
17
README.md
|
@ -64,7 +64,6 @@ GLOBAL OPTIONS:
|
|||
--force-non-ssl, -f force allow use of non-ssl
|
||||
--username value, -u value username 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")
|
||||
--skip-ping skip pinging the registry while establishing connection
|
||||
--help, -h show help
|
||||
|
@ -87,7 +86,7 @@ not present, you can pass through flags directly.
|
|||
|
||||
```console
|
||||
# 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
|
||||
REPO TAGS
|
||||
awscli latest
|
||||
|
@ -100,7 +99,7 @@ chrome beta, latest, stable
|
|||
**Tags**
|
||||
|
||||
```console
|
||||
$ reg tags tor-browser
|
||||
$ reg tags r.j3ss.co/tor-browser
|
||||
alpha
|
||||
hardened
|
||||
latest
|
||||
|
@ -110,7 +109,7 @@ stable
|
|||
### Get a Manifest
|
||||
|
||||
```console
|
||||
$ reg manifest htop
|
||||
$ reg manifest r.j3ss.co/htop
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"name": "htop",
|
||||
|
@ -130,30 +129,30 @@ $ reg manifest htop
|
|||
|
||||
### Get the Digest
|
||||
```console
|
||||
$ reg digest htop
|
||||
$ reg digest r.j3ss.co/htop
|
||||
sha256:791158756cc0f5b27ef8c5c546284568fc9b7f4cf1429fb736aff3ee2d2e340f
|
||||
```
|
||||
|
||||
### Download a Layer
|
||||
|
||||
```console
|
||||
$ reg layer -o chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
|
||||
$ reg layer -o r.j3ss.co/chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
|
||||
OR
|
||||
$ reg layer chrome@sha256:a3ed95caeb0.. > layer.tar
|
||||
$ reg layer r.j3ss.co/chrome@sha256:a3ed95caeb0.. > layer.tar
|
||||
```
|
||||
|
||||
|
||||
### Delete an Image
|
||||
|
||||
```console
|
||||
$ reg rm chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
|
||||
$ reg rm r.j3ss.co/chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
|
||||
Deleted chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
|
||||
```
|
||||
|
||||
### Vulnerability Reports
|
||||
|
||||
```console
|
||||
$ reg vulns --clair https://clair.j3ss.co chrome
|
||||
$ reg vulns --clair https://clair.j3ss.co r.j3ss.co/chrome
|
||||
Found 32 vulnerabilities
|
||||
CVE-2015-5180: [Low]
|
||||
|
||||
|
|
27
delete.go
27
delete.go
|
@ -3,7 +3,7 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/genuinetools/reg/repoutils"
|
||||
"github.com/genuinetools/reg/registry"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
@ -16,15 +16,32 @@ var deleteCommand = cli.Command{
|
|||
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
|
||||
}
|
||||
|
||||
if err := r.Delete(repo, ref); err != nil {
|
||||
return fmt.Errorf("Delete %s@%s failed: %v", repo, ref, err)
|
||||
// Create the registry client.
|
||||
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
|
||||
},
|
||||
|
|
|
@ -1,37 +1,27 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
// Make sure we have busybox in list.
|
||||
out, err := run("ls")
|
||||
out, err := run("ls", domain)
|
||||
if err != nil {
|
||||
t.Fatalf("output: %s, error: %v", string(out), err)
|
||||
}
|
||||
expected := []string{"alpine latest", "busybox glibc, musl, latest"}
|
||||
for _, e := range expected {
|
||||
if !strings.Contains(out, e) {
|
||||
t.Logf("expected to contain: %s\ngot: %s", e, out)
|
||||
t.Fatalf("output: %s, error: %v", out, err)
|
||||
}
|
||||
expected := `REPO TAGS
|
||||
alpine 3.5, latest
|
||||
busybox glibc, latest, musl`
|
||||
if !strings.HasSuffix(strings.TrimSpace(out), expected) {
|
||||
t.Fatalf("expected to contain: %s\ngot: %s", expected, out)
|
||||
}
|
||||
|
||||
// Remove busybox image.
|
||||
if out, err := run("rm", "busybox"); err != nil {
|
||||
t.Fatalf("output: %s, error: %v", string(out), err)
|
||||
if out, err := run("rm", fmt.Sprintf("%s/busybox:glibc", domain)); err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
15
digest.go
15
digest.go
|
@ -3,7 +3,7 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/genuinetools/reg/repoutils"
|
||||
"github.com/genuinetools/reg/registry"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
@ -15,17 +15,24 @@ var digestCommand = cli.Command{
|
|||
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
|
||||
}
|
||||
|
||||
digest, err := r.Digest(repo, ref)
|
||||
// Create the registry client.
|
||||
r, err := createRegistryClient(c, image.Domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(digest)
|
||||
// Get the digest.
|
||||
digest, err := r.Digest(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(digest.String())
|
||||
|
||||
return nil
|
||||
},
|
||||
|
|
20
layer.go
20
layer.go
|
@ -5,8 +5,7 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/genuinetools/reg/repoutils"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/genuinetools/reg/registry"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
@ -25,12 +24,25 @@ var layerCommand = cli.Command{
|
|||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
33
layer_test.go
Normal file
33
layer_test.go
Normal 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
61
list.go
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
|
@ -15,13 +16,46 @@ var listCommand = cli.Command{
|
|||
Aliases: []string{"ls"},
|
||||
Usage: "list all repositories",
|
||||
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.
|
||||
repos, err := r.Catalog("")
|
||||
if err != nil {
|
||||
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.
|
||||
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
|
||||
|
@ -29,31 +63,10 @@ var listCommand = cli.Command{
|
|||
// Print header.
|
||||
fmt.Fprintln(w, "REPO\tTAGS")
|
||||
|
||||
var (
|
||||
l sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
wg.Add(len(repos))
|
||||
// Sort the repos.
|
||||
for _, repo := range repos {
|
||||
go func(repo string) {
|
||||
// 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)
|
||||
w.Write([]byte(fmt.Sprintf("%s\t%s\n", repo, strings.Join(repoTags[repo], ", "))))
|
||||
}
|
||||
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()
|
||||
|
||||
|
|
15
list_test.go
15
list_test.go
|
@ -6,14 +6,15 @@ import (
|
|||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
out, err := run("ls")
|
||||
out, err := run("ls", domain)
|
||||
if err != nil {
|
||||
t.Fatalf("output: %s, error: %v", string(out), err)
|
||||
}
|
||||
expected := []string{"alpine latest", "busybox glibc, musl"}
|
||||
for _, e := range expected {
|
||||
if !strings.Contains(out, e) {
|
||||
t.Logf("expected to contain: %s\ngot: %s", e, out)
|
||||
t.Fatalf("output: %s, error: %v", out, err)
|
||||
}
|
||||
|
||||
expected := `REPO TAGS
|
||||
alpine 3.5, latest
|
||||
busybox glibc, latest, musl`
|
||||
if !strings.HasSuffix(strings.TrimSpace(out), expected) {
|
||||
t.Fatalf("expected to contain: %s\ngot: %s", expected, out)
|
||||
}
|
||||
}
|
||||
|
|
63
main.go
63
main.go
|
@ -6,7 +6,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/genuinetools/reg/registry"
|
||||
"github.com/genuinetools/reg/repoutils"
|
||||
"github.com/genuinetools/reg/version"
|
||||
|
@ -14,11 +13,6 @@ import (
|
|||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
auth types.AuthConfig
|
||||
r *registry.Registry
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "reg"
|
||||
|
@ -48,12 +42,6 @@ func main() {
|
|||
Name: "password, p",
|
||||
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{
|
||||
Name: "timeout",
|
||||
Value: "1m",
|
||||
|
@ -90,33 +78,36 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
auth, err = repoutils.GetAuthConfig(c.GlobalString("username"), c.GlobalString("password"), c.GlobalString("registry"))
|
||||
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
|
||||
return
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,6 +13,10 @@ import (
|
|||
"github.com/genuinetools/reg/testutils"
|
||||
)
|
||||
|
||||
const (
|
||||
domain = "localhost:5000"
|
||||
)
|
||||
|
||||
var (
|
||||
exeSuffix string // ".exe" on Windows
|
||||
|
||||
|
@ -103,8 +107,9 @@ func TestMain(m *testing.M) {
|
|||
func run(args ...string) (string, error) {
|
||||
prog := "./testreg" + exeSuffix
|
||||
// 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.Env = []string{"REG_REGISTRY=localhost:5000"}
|
||||
out, err := cmd.CombinedOutput()
|
||||
return string(out), err
|
||||
}
|
||||
|
|
14
manifest.go
14
manifest.go
|
@ -4,7 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/genuinetools/reg/repoutils"
|
||||
"github.com/genuinetools/reg/registry"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
@ -22,7 +22,13 @@ var manifestCommand = cli.Command{
|
|||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -30,13 +36,13 @@ var manifestCommand = cli.Command{
|
|||
var manifest interface{}
|
||||
if c.Bool("v1") {
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Get the v2 manifest.
|
||||
manifest, err = r.Manifest(repo, ref)
|
||||
manifest, err = r.Manifest(image.Path, image.Reference())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
31
manifest_test.go
Normal file
31
manifest_test.go
Normal 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)
|
||||
}
|
||||
}
|
|
@ -5,24 +5,12 @@ import (
|
|||
"net/http"
|
||||
|
||||
"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
|
||||
func (r *Registry) Delete(repository, digest string) 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.
|
||||
func (r *Registry) Delete(repository string, digest digest.Digest) (err error) {
|
||||
url := r.url("/v2/%s/manifests/%s", repository, digest)
|
||||
r.Logf("registry.manifests.delete url=%s repository=%s digest=%s",
|
||||
url, repository, digest)
|
||||
|
|
|
@ -5,20 +5,26 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// Digest returns the digest for a repository and reference.
|
||||
func (r *Registry) Digest(repository, ref string) (string, error) {
|
||||
url := r.url("/v2/%s/manifests/%s", repository, ref)
|
||||
// Digest returns the digest for an image.
|
||||
func (r *Registry) Digest(image Image) (digest.Digest, error) {
|
||||
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",
|
||||
url, repository, ref)
|
||||
url, image.Path, image.Tag)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Accept", schema2.MediaTypeManifest)
|
||||
|
||||
req.Header.Set("Accept", schema2.MediaTypeManifest)
|
||||
resp, err := r.Client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -29,6 +35,5 @@ func (r *Registry) Digest(repository, ref string) (string, error) {
|
|||
return "", fmt.Errorf("Got status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
digest := resp.Header.Get("Docker-Content-Digest")
|
||||
return digest, nil
|
||||
return digest.FromString(resp.Header.Get("Docker-Content-Digest")), nil
|
||||
}
|
||||
|
|
67
registry/image.go
Normal file
67
registry/image.go
Normal 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
|
||||
}
|
|
@ -87,17 +87,17 @@ func (r *Registry) ManifestV1(repository, ref string) (schema1.SignedManifest, e
|
|||
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 {
|
||||
url := r.url("/v2/%s/manifests/%s", 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer := bytes.NewBuffer(manifestJson)
|
||||
req, err := http.NewRequest("PUT", url, buffer)
|
||||
req, err := http.NewRequest("PUT", url, bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
16
tags.go
16
tags.go
|
@ -2,8 +2,10 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/genuinetools/reg/registry"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
@ -15,11 +17,23 @@ var tagsCommand = cli.Command{
|
|||
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 {
|
||||
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.
|
||||
fmt.Println(strings.Join(tags, "\n"))
|
||||
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTags(t *testing.T) {
|
||||
out, err := run("tags", "busybox")
|
||||
out, err := run("tags", fmt.Sprintf("%s/busybox", domain))
|
||||
if err != nil {
|
||||
t.Fatalf("output: %s, error: %v", string(out), err)
|
||||
t.Fatalf("output: %s, error: %v", out, err)
|
||||
}
|
||||
expected := `glibc
|
||||
latest
|
||||
musl
|
||||
`
|
||||
if !strings.HasSuffix(out, expected) {
|
||||
t.Logf("expected: %s\ngot: %s", expected, out)
|
||||
t.Fatalf("expected: %s\ngot: %s", expected, out)
|
||||
}
|
||||
}
|
||||
|
|
12
vulns.go
12
vulns.go
|
@ -6,7 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/genuinetools/reg/clair"
|
||||
"github.com/genuinetools/reg/repoutils"
|
||||
"github.com/genuinetools/reg/registry"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
@ -38,7 +38,13 @@ var vulnsCommand = cli.Command{
|
|||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -60,7 +66,7 @@ var vulnsCommand = cli.Command{
|
|||
}
|
||||
|
||||
// Get the vulnerability report.
|
||||
report, err := cr.Vulnerabilities(r, repo, ref)
|
||||
report, err := cr.Vulnerabilities(r, image.Path, image.Reference())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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 {
|
||||
t.Fatalf("output: %s, error: %v", string(out), err)
|
||||
t.Fatalf("output: %s, error: %v", out, err)
|
||||
}
|
||||
|
||||
expected := `clair.clair resp.Status=200 OK`
|
||||
if !strings.HasSuffix(strings.TrimSpace(out), expected) {
|
||||
t.Logf("expected: %s\ngot: %s", expected, out)
|
||||
t.Fatalf("expected: %s\ngot: %s", expected, out)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue