mirror of
https://github.com/genuinetools/reg.git
synced 2024-05-12 09:28: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
|
--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]
|
||||||
|
|
||||||
|
|
27
delete.go
27
delete.go
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
15
digest.go
15
digest.go
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
20
layer.go
20
layer.go
|
@ -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
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 (
|
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()
|
||||||
|
|
||||||
|
|
15
list_test.go
15
list_test.go
|
@ -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
63
main.go
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
14
manifest.go
14
manifest.go
|
@ -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
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"
|
"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)
|
||||||
|
|
|
@ -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
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
|
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
16
tags.go
|
@ -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"))
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
vulns.go
12
vulns.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue