update vendor

Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
Jess Frazelle 2018-09-25 12:27:46 -04:00
parent 19a32db84d
commit 94d1cfbfbf
No known key found for this signature in database
GPG key ID: 18F3685C0022BFF3
10501 changed files with 2307943 additions and 29279 deletions

View file

@ -43,11 +43,15 @@ Icon
.Trashes
reg
server/server
testreg
.certs
cross/
# Go coverage results
coverage.txt
profile.out
!go.mod
testreg
.certs
server/static/index.html
server/static/repo/

10
vendor/github.com/genuinetools/reg/.goosarch generated vendored Normal file
View file

@ -0,0 +1,10 @@
darwin/amd64
darwin/386
freebsd/amd64
freebsd/386
linux/arm
linux/arm64
linux/amd64
linux/386
windows/amd64
windows/386

View file

@ -1,62 +1,56 @@
---
language: go
sudo: true
notifications:
email: true
go:
- 1.x
- tip
services:
- docker
env:
global:
- GO15VENDOREXPERIMENT=1
matrix:
allow_failures:
- go: tip
fast_finish: true
install:
- go get github.com/golang/lint/golint
- go get honnef.co/go/tools/cmd/staticcheck
script:
- go build -v
- go vet $(go list ./... | grep -v vendor)
- staticcheck $(go list ./... | grep -v vendor)
- test -z "$(golint ./... | grep -v vendor | tee /dev/stderr)"
- test -z "$(gofmt -s -l . | grep -v vendor | tee /dev/stderr)"
- DOCKER_API_VERSION=1.35 make dind dtest
- make release
after_success:
- bash <(curl -s https://codecov.io/bash)
deploy:
provider: releases
api_key:
secure: "xz4uJ+vrF5+u3zucCPdnoXR/a0i8/oUzzDABuKiaB9AFFjrM8obLYo2AgMlP5zj/YHpGgTP51m/sx/qwJKfNvCoR2alBb2taYzJnhCfXzOuviZ0RZbM2LqA72lutdAzZ5eyMPCXcqvOjf6INnCmqQeJjDWo8UzGKSlWP2cqU/Qovs1vzurImME86DjqQ4EDaYlZS3tVc5BtEqmhylT2q0aO7gNJcMunDJpIEwb3vo8bbOoS6heQO2DVFf553lnZTSheEOERiF8r/O3vdMBCIqq7Xr2WIzJ4WGoNqzCk4sVcOZYP1yWa4Je/J09TaM8Uam+SZCG8p2lG+lr9toNv9jDHAA3Z986hAj+1NhRXTbwtRYM/KfL38UegvGfFCRvOAc+3AQhQaw1p2hX599in4zl/IcSVjF6IytJGj+JrCHU1p5Bd9qphFQKlXAXQKZwH+TKt3QTnrUQIUOn0QwcfgbvDUaA2XMsR9f0BWNshILvz79JJZmwXY7C7ufVSKdL+T+9dNn/5N7dMn6fWb7ZruwK3N6gLyVSulMinSYyNIHGiEH3mdoBr020KYD1w1+cfK4Ov6B8vf9k7atzHDPRklm2X0hvda2T0UXOv5+hr+OlvdhpqZKDB2HkVOUQUUfk7cL88u+FpU6pktlhJVLSCl292jWS05I1AYOiHChEFONeE="
file:
- cross/reg-linux-amd64.md5
- cross/reg-windows-386.sha256
- cross/reg-linux-arm
- cross/reg-darwin-amd64.md5
- cross/reg-darwin-amd64
- cross/reg-linux-arm64.sha256
- cross/reg-linux-arm.sha256
- cross/reg-linux-386.sha256
- cross/reg-darwin-386.md5
- cross/reg-windows-386.md5
- cross/reg-linux-arm64.md5
- cross/reg-linux-arm64
- cross/reg-linux-amd64.sha256
- cross/reg-linux-386.md5
- cross/reg-windows-amd64
- cross/reg-windows-amd64.md5
- cross/reg-windows-amd64.sha256
- cross/reg-linux-arm.md5
- cross/reg-darwin-386.sha256
- cross/reg-darwin-amd64.sha256
- cross/reg-windows-386
- cross/reg-darwin-386
- cross/reg-linux-386
- cross/reg-linux-amd64
skip_cleanup: true
on:
tags: true
language: go
sudo: true
services:
- docker
go:
- 1.10.x
before_install:
- go get github.com/golang/lint/golint
- go get honnef.co/go/tools/cmd/staticcheck
jobs:
include:
- script:
- make fmt lint staticcheck vet install
- DOCKER_API_VERSION=1.38 make dind dtest
- stage: Build Release
script:
- make release
- echo "Deploying to GitHub releases ..."
deploy:
provider: releases
file:
- cross/reg-darwin-amd64
- cross/reg-darwin-amd64.md5
- cross/reg-darwin-amd64.sha256
- cross/reg-darwin-386
- cross/reg-darwin-386.md5
- cross/reg-darwin-386.sha256
- cross/reg-freebsd-amd64
- cross/reg-freebsd-amd64.md5
- cross/reg-freebsd-amd64.sha256
- cross/reg-freebsd-386
- cross/reg-freebsd-386.md5
- cross/reg-freebsd-386.sha256
- cross/reg-linux-arm
- cross/reg-linux-arm.md5
- cross/reg-linux-arm.sha256
- cross/reg-linux-arm64
- cross/reg-linux-arm64.md5
- cross/reg-linux-arm64.sha256
- cross/reg-linux-amd64
- cross/reg-linux-amd64.md5
- cross/reg-linux-amd64.sha256
- cross/reg-linux-386
- cross/reg-linux-386.md5
- cross/reg-linux-386.sha256
- cross/reg-windows-amd64
- cross/reg-windows-amd64.md5
- cross/reg-windows-amd64.sha256
- cross/reg-windows-386
- cross/reg-windows-386.md5
- cross/reg-windows-386.sha256
skip_cleanup: true
on:
tags: true
api_key:
secure: "AULDRJQ8olD4R3v35sCnSZx136DnJnqMkX3ANNC/gosQhI+sFViW7BT0kaQBcIqBjF3jrik4Zm4BAW0VSZruwahD+pxjf/uroYAraMaFohw5SCL+CIY4qgNM/kkkZXTAP5DvPIOKEmXY8FfL1ZD0C6B8OKabRjh2rxuoh+enjflrA/B6B98yqo/NufCgfqfhwSK7xnh7kY4DCwfpQ80fQFzwj1BQQ5bBpP7tTVpZVqnbfSTSUoV8pX+2Ef/+t3KgYWx11+zkML9GbLevf7SbaMP9qceLvSW2npjzK1vCdQEtKp5OEuvaqsHXpdV58EdQx01zt5RSKSj/EcVaASwt2dbR10kTV7hBTvNqmvStjjKavCKzyohHG5s8VOrTXnIezuxXe1l9P9teIktY+uFClsC9t8jkQZZlVYn8brWMp+oU1VtVgYQRiF4p08IxUUS5DtZ1ZML9Yqh0am1lo2EFRydOBiIVoG2cSO8IbvDYYhIUrfc+pZ01vBV+sp+5EGmNp/7wgLwNSzoAgjh6BRLZO7irICcd579L4ZLWYX8k7/oXFDb8ABjzl0/Fk4K6EL1TTQzJaabxVAT1SI7b2PnqhJx7f5xPbNdNIQAQ/+I0sN7hpIJ0o/VaqObGKIdJeZSWISQ5TTlK+TtbucAtNaSgw0rhNYrIslK2bfgODa6MfJc="

1
vendor/github.com/genuinetools/reg/.traviskey generated vendored Normal file
View file

@ -0,0 +1 @@
AULDRJQ8olD4R3v35sCnSZx136DnJnqMkX3ANNC/gosQhI+sFViW7BT0kaQBcIqBjF3jrik4Zm4BAW0VSZruwahD+pxjf/uroYAraMaFohw5SCL+CIY4qgNM/kkkZXTAP5DvPIOKEmXY8FfL1ZD0C6B8OKabRjh2rxuoh+enjflrA/B6B98yqo/NufCgfqfhwSK7xnh7kY4DCwfpQ80fQFzwj1BQQ5bBpP7tTVpZVqnbfSTSUoV8pX+2Ef/+t3KgYWx11+zkML9GbLevf7SbaMP9qceLvSW2npjzK1vCdQEtKp5OEuvaqsHXpdV58EdQx01zt5RSKSj/EcVaASwt2dbR10kTV7hBTvNqmvStjjKavCKzyohHG5s8VOrTXnIezuxXe1l9P9teIktY+uFClsC9t8jkQZZlVYn8brWMp+oU1VtVgYQRiF4p08IxUUS5DtZ1ZML9Yqh0am1lo2EFRydOBiIVoG2cSO8IbvDYYhIUrfc+pZ01vBV+sp+5EGmNp/7wgLwNSzoAgjh6BRLZO7irICcd579L4ZLWYX8k7/oXFDb8ABjzl0/Fk4K6EL1TTQzJaabxVAT1SI7b2PnqhJx7f5xPbNdNIQAQ/+I0sN7hpIJ0o/VaqObGKIdJeZSWISQ5TTlK+TtbucAtNaSgw0rhNYrIslK2bfgODa6MfJc=

View file

@ -23,10 +23,12 @@ RUN set -x \
&& rm -rf /go \
&& echo "Build complete."
FROM scratch
FROM alpine:latest
COPY --from=builder /usr/bin/reg /usr/bin/reg
COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs
WORKDIR /src
ENTRYPOINT [ "reg" ]
CMD [ "--help" ]

11
vendor/github.com/genuinetools/reg/Dockerfile.clair generated vendored Normal file
View file

@ -0,0 +1,11 @@
FROM quay.io/coreos/clair
RUN apk add --no-cache \
ca-certificates
COPY testutils/snakeoil/cert.pem /usr/local/share/ca-certificates/clair.pem
# normally we'd use update-ca-certificates, but something about running it in
# Alpine is off, and the certs don't get added. Fortunately, we only need to
# add ca-certificates to the global store and it's all plain text.
RUN cat /usr/local/share/ca-certificates/* >> /etc/ssl/certs/ca-certificates.crt

View file

@ -1,310 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/Azure/go-ansiterm"
packages = [
".",
"winterm"
]
revision = "d6e3b3328b783f23731bc4d058875b0371ff8109"
[[projects]]
name = "github.com/Microsoft/go-winio"
packages = ["."]
revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f"
version = "v0.4.7"
[[projects]]
branch = "master"
name = "github.com/Nvveen/Gotty"
packages = ["."]
revision = "cd527374f1e5bff4938207604a14f2e38a9cf512"
[[projects]]
branch = "master"
name = "github.com/beorn7/perks"
packages = ["quantile"]
revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9"
[[projects]]
branch = "master"
name = "github.com/containerd/continuity"
packages = ["pathdriver"]
revision = "d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371"
[[projects]]
branch = "master"
name = "github.com/docker/cli"
packages = [
"cli/config/configfile",
"cli/config/credentials",
"opts"
]
revision = "2731c71c993a4d1e43101eea56e7c343315024f1"
[[projects]]
branch = "master"
name = "github.com/docker/distribution"
packages = [
".",
"digestset",
"manifest",
"manifest/manifestlist",
"manifest/schema1",
"manifest/schema2",
"metrics",
"reference",
"registry/api/errcode",
"registry/api/v2",
"registry/client",
"registry/client/auth",
"registry/client/auth/challenge",
"registry/client/transport",
"registry/storage/cache",
"registry/storage/cache/memory"
]
revision = "6fca8d6e6713acbdf3f9ca40cf6370fc5ee5ee53"
[[projects]]
branch = "master"
name = "github.com/docker/docker"
packages = [
"api",
"api/types",
"api/types/blkiodev",
"api/types/container",
"api/types/events",
"api/types/filters",
"api/types/image",
"api/types/mount",
"api/types/network",
"api/types/registry",
"api/types/strslice",
"api/types/swarm",
"api/types/swarm/runtime",
"api/types/time",
"api/types/versions",
"api/types/volume",
"client",
"errdefs",
"pkg/homedir",
"pkg/idtools",
"pkg/ioutils",
"pkg/jsonmessage",
"pkg/longpath",
"pkg/mount",
"pkg/stringid",
"pkg/system",
"pkg/tarsum",
"pkg/term",
"pkg/term/windows",
"registry",
"registry/resumable"
]
revision = "a575b0b1384b2ba89b79cbd7e770fbeb616758b3"
[[projects]]
branch = "master"
name = "github.com/docker/docker-ce"
packages = ["components/cli/cli/config"]
revision = "0b63fed1587e3fedd1ab72d9f6e80f71e8c52f8b"
[[projects]]
name = "github.com/docker/docker-credential-helpers"
packages = [
"client",
"credentials",
"pass"
]
revision = "d68f9aeca33f5fd3f08eeae5e9d175edf4e731d1"
version = "v0.6.0"
[[projects]]
branch = "master"
name = "github.com/docker/go-connections"
packages = [
"nat",
"sockets",
"tlsconfig"
]
revision = "7395e3f8aa162843a74ed6d48e79627d9792ac55"
[[projects]]
branch = "master"
name = "github.com/docker/go-metrics"
packages = ["."]
revision = "399ea8c73916000c64c2c76e8da00ca82f8387ab"
[[projects]]
branch = "master"
name = "github.com/docker/go-units"
packages = ["."]
revision = "47565b4f722fb6ceae66b95f853feed578a4a51c"
[[projects]]
branch = "master"
name = "github.com/docker/libtrust"
packages = ["."]
revision = "aabc10ec26b754e797f9028f4589c5b7bd90dc20"
[[projects]]
name = "github.com/gogo/protobuf"
packages = ["proto"]
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
version = "v1.0.0"
[[projects]]
name = "github.com/golang/protobuf"
packages = ["proto"]
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
version = "v1.0.0"
[[projects]]
name = "github.com/google/go-cmp"
packages = [
"cmp",
"cmp/internal/diff",
"cmp/internal/function",
"cmp/internal/value"
]
revision = "3af367b6b30c263d47e8895973edcca9a49cf029"
version = "v0.2.0"
[[projects]]
name = "github.com/gorilla/context"
packages = ["."]
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
version = "v1.1"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
revision = "53c1911da2b537f792e7cafcb446b05ffe33b996"
version = "v1.6.1"
[[projects]]
name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"]
revision = "3247c84500bff8d9fb6d579d800f20b3e091582c"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/mitchellh/go-wordwrap"
packages = ["."]
revision = "ad45545899c7b13c020ea92b2072220eefad42b8"
[[projects]]
name = "github.com/opencontainers/go-digest"
packages = ["."]
revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf"
version = "v1.0.0-rc1"
[[projects]]
name = "github.com/opencontainers/image-spec"
packages = [
"specs-go",
"specs-go/v1"
]
revision = "d60099175f88c47cd379c4738d158884749ed235"
version = "v1.0.1"
[[projects]]
name = "github.com/opencontainers/runc"
packages = ["libcontainer/user"]
revision = "baf6536d6259209c3edfa2b22237af82942d3dfa"
version = "v0.1.1"
[[projects]]
name = "github.com/peterhellberg/link"
packages = ["."]
revision = "d1cebc7ea14a5fc0de7cb4a45acae773161642c6"
version = "v1.0.0"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
branch = "master"
name = "github.com/prometheus/client_golang"
packages = [
"prometheus",
"prometheus/promhttp"
]
revision = "c3324c1198cf3374996e9d3098edd46a6b55afc9"
[[projects]]
branch = "master"
name = "github.com/prometheus/client_model"
packages = ["go"]
revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c"
[[projects]]
branch = "master"
name = "github.com/prometheus/common"
packages = [
"expfmt",
"internal/bitbucket.org/ww/goautoneg",
"model"
]
revision = "6fb6fce6f8b75884b92e1889c150403fc0872c5e"
[[projects]]
branch = "master"
name = "github.com/prometheus/procfs"
packages = [
".",
"internal/util",
"nfs",
"xfs"
]
revision = "54d17b57dd7d4a3aa092476596b3f8a933bde349"
[[projects]]
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "d682213848ed68c0a260ca37d6dd5ace8423f5ba"
version = "v1.0.4"
[[projects]]
branch = "master"
name = "github.com/urfave/cli"
packages = ["."]
revision = "8e01ec4cd3e2d84ab2fe90d8210528ffbb06d8ff"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
revision = "c7dcf104e3a7a1417abc0230cb0d5240d764159d"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = [
"context",
"context/ctxhttp",
"proxy"
]
revision = "d0aafc73d5cdc42264b0af071c261abac580695e"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = [
"unix",
"windows"
]
revision = "7dca6fe1f43775aa6d1334576870ff63f978f539"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "5cdf10e821ede3dcf4207f6e2b0e5abe31ac803e81704a82c798b0199c057882"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -1,65 +0,0 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[override]]
name = "github.com/Sirupsen/logrus"
source = "github.com/sirupsen/logrus"
[[constraint]]
name = "github.com/docker/distribution"
branch = "master"
[[constraint]]
name = "github.com/docker/docker"
branch = "master"
[[constraint]]
name = "github.com/docker/docker-ce"
branch = "master"
[[override]]
name = "github.com/docker/cli"
branch = "master"
[[override]]
name = "github.com/docker/docker-credentials-helpers"
branch = "master"
[[override]]
name = "github.com/docker/go-connections"
branch = "master"
[[override]]
name = "github.com/docker/go-units"
branch = "master"
[[constraint]]
name = "github.com/urfave/cli"
branch = "master"
[[constraint]]
name = "github.com/opencontainers/go-digest"
branch = "master"
[[override]]
name = "github.com/prometheus/client_golang"
branch = "master"

View file

@ -1,165 +1,26 @@
# Set an output prefix, which is the local directory if not specified
PREFIX?=$(shell pwd)
# Setup name variables for the package/tool
NAME := reg
PKG := github.com/genuinetools/$(NAME)
# Set any default go build tags
CGO_ENABLED := 0
# Set any default go build tags.
BUILDTAGS :=
# Set the build dir, where built cross-compiled binaries will be output
BUILDDIR := ${PREFIX}/cross
include basic.mk
# Populate version variables
# Add to compile time flags
VERSION := $(shell cat VERSION.txt)
GITCOMMIT := $(shell git rev-parse --short HEAD)
GITUNTRACKEDCHANGES := $(shell git status --porcelain --untracked-files=no)
ifneq ($(GITUNTRACKEDCHANGES),)
GITCOMMIT := $(GITCOMMIT)-dirty
endif
CTIMEVAR=-X $(PKG)/version.GITCOMMIT=$(GITCOMMIT) -X $(PKG)/version.VERSION=$(VERSION)
GO_LDFLAGS=-ldflags "-w $(CTIMEVAR)"
GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static"
# List the GOOS and GOARCH to build
GOOSARCHES = darwin/amd64 darwin/386 linux/arm linux/arm64 linux/amd64 linux/386 windows/amd64 windows/386
all: clean build fmt lint test staticcheck vet install ## Runs a clean, build, fmt, lint, test, staticcheck, vet and install
.PHONY: build
build: $(NAME) ## Builds a dynamic executable or package
$(NAME): *.go VERSION.txt
@echo "+ $@"
go build -tags "$(BUILDTAGS)" ${GO_LDFLAGS} -o $(NAME) .
.PHONY: static
static: ## Builds a static executable
@echo "+ $@"
CGO_ENABLED=0 go build \
-tags "$(BUILDTAGS) static_build" \
${GO_LDFLAGS_STATIC} -o $(NAME) .
.PHONY: fmt
fmt: ## Verifies all files have men `gofmt`ed
@echo "+ $@"
@gofmt -s -l . | grep -v '.pb.go:' | grep -v vendor | tee /dev/stderr
.PHONY: lint
lint: ## Verifies `golint` passes
@echo "+ $@"
@golint ./... | grep -v '.pb.go:' | grep -v vendor | tee /dev/stderr
.PHONY: test
test: ## Runs the go tests
@echo "+ $@"
@go test -v -tags "$(BUILDTAGS) cgo" $(shell go list ./... | grep -v vendor)
.PHONY: vet
vet: ## Verifies `go vet` passes
@echo "+ $@"
@go vet $(shell go list ./... | grep -v vendor) | grep -v '.pb.go:' | tee /dev/stderr
.PHONY: staticcheck
staticcheck: ## Verifies `staticcheck` passes
@echo "+ $@"
@staticcheck $(shell go list ./... | grep -v vendor) | grep -v '.pb.go:' | tee /dev/stderr
.PHONY: cover
cover: ## Runs go test with coverage
@echo "" > coverage.txt
@for d in $(shell go list ./... | grep -v vendor); do \
go test -race -coverprofile=profile.out -covermode=atomic "$$d"; \
if [ -f profile.out ]; then \
cat profile.out >> coverage.txt; \
rm profile.out; \
fi; \
done;
.PHONY: install
install: ## Installs the executable or package
@echo "+ $@"
go install -a -tags "$(BUILDTAGS)" ${GO_LDFLAGS} .
define buildpretty
mkdir -p $(BUILDDIR)/$(1)/$(2);
GOOS=$(1) GOARCH=$(2) CGO_ENABLED=0 go build \
-o $(BUILDDIR)/$(1)/$(2)/$(NAME) \
-a -tags "$(BUILDTAGS) static_build netgo" \
-installsuffix netgo ${GO_LDFLAGS_STATIC} .;
md5sum $(BUILDDIR)/$(1)/$(2)/$(NAME) > $(BUILDDIR)/$(1)/$(2)/$(NAME).md5;
sha256sum $(BUILDDIR)/$(1)/$(2)/$(NAME) > $(BUILDDIR)/$(1)/$(2)/$(NAME).sha256;
endef
.PHONY: cross
cross: *.go VERSION.txt ## Builds the cross-compiled binaries, creating a clean directory structure (eg. GOOS/GOARCH/binary)
@echo "+ $@"
$(foreach GOOSARCH,$(GOOSARCHES), $(call buildpretty,$(subst /,,$(dir $(GOOSARCH))),$(notdir $(GOOSARCH))))
define buildrelease
GOOS=$(1) GOARCH=$(2) CGO_ENABLED=0 go build \
-o $(BUILDDIR)/$(NAME)-$(1)-$(2) \
-a -tags "$(BUILDTAGS) static_build netgo" \
-installsuffix netgo ${GO_LDFLAGS_STATIC} .;
md5sum $(BUILDDIR)/$(NAME)-$(1)-$(2) > $(BUILDDIR)/$(NAME)-$(1)-$(2).md5;
sha256sum $(BUILDDIR)/$(NAME)-$(1)-$(2) > $(BUILDDIR)/$(NAME)-$(1)-$(2).sha256;
endef
.PHONY: release
release: *.go VERSION.txt ## Builds the cross-compiled binaries, naming them in such a way for release (eg. binary-GOOS-GOARCH)
@echo "+ $@"
$(foreach GOOSARCH,$(GOOSARCHES), $(call buildrelease,$(subst /,,$(dir $(GOOSARCH))),$(notdir $(GOOSARCH))))
.PHONY: bump-version
BUMP := patch
bump-version: ## Bump the version in the version file. Set BUMP to [ patch | major | minor ]
@go get -u github.com/jessfraz/junk/sembump # update sembump tool
$(eval NEW_VERSION = $(shell sembump --kind $(BUMP) $(VERSION)))
@echo "Bumping VERSION.txt from $(VERSION) to $(NEW_VERSION)"
echo $(NEW_VERSION) > VERSION.txt
@echo "Updating links to download binaries in README.md"
sed -i s/$(VERSION)/$(NEW_VERSION)/g README.md
git add VERSION.txt README.md
git commit -vsam "Bump version to $(NEW_VERSION)"
@echo "Run make tag to create and push the tag for new version $(NEW_VERSION)"
.PHONY: tag
tag: ## Create a new git tag to prepare to build a release
git tag -sa $(VERSION) -m "$(VERSION)"
@echo "Run git push origin $(VERSION) to push your new tag to GitHub and trigger a travis build."
.PHONY: clean
clean: ## Cleanup any build binaries or packages
@echo "+ $@"
$(RM) $(NAME)
$(RM) -r $(BUILDDIR)
$(RM) -r $(CURDIR)/.certs
# set the graph driver as the current graphdriver if not set
DOCKER_GRAPHDRIVER := $(if $(DOCKER_GRAPHDRIVER),$(DOCKER_GRAPHDRIVER),$(shell docker info 2>&1 | grep "Storage Driver" | sed 's/.*: //'))
export DOCKER_GRAPHDRIVER
# if this session isn't interactive, then we don't want to allocate a
# TTY, which would fail, but if it is interactive, we do want to attach
# so that the user can send e.g. ^C through.
INTERACTIVE := $(shell [ -t 0 ] && echo 1 || echo 0)
ifeq ($(INTERACTIVE), 1)
DOCKER_FLAGS += -t
endif
.PHONY: prebuild
prebuild:
.PHONY: dind
DIND_CONTAINER=reg-dind
DIND_DOCKER_IMAGE=r.j3ss.co/docker:userns
dind: ## Starts a docker-in-docker container for running the tests with
dind: stop-dind ## Starts a docker-in-docker container for running the tests with.
docker run -d \
--name $(DIND_CONTAINER) \
--name $(NAME)-dind \
--privileged \
-v $(CURDIR)/.certs:/etc/docker/ssl \
-v $(CURDIR):/go/src/github.com/genuinetools/reg \
-v /tmp:/tmp \
$(DIND_DOCKER_IMAGE) \
$(REGISTRY)/docker:userns \
dockerd -D --storage-driver $(DOCKER_GRAPHDRIVER) \
-H tcp://127.0.0.1:2375 \
--host=unix:///var/run/docker.sock \
@ -170,23 +31,32 @@ dind: ## Starts a docker-in-docker container for running the tests with
--tlskey=/etc/docker/ssl/server.key \
--tlscert=/etc/docker/ssl/server.cert
.PHONY: stop-dind
stop-dind: ## Stops the docker-in-docker container.
@docker rm -f $(NAME)-dind >/dev/null 2>&1 || true
.PHONY: image-dev
image-dev:
docker build --rm --force-rm -f Dockerfile.dev -t $(REGISTRY)/$(NAME):dev .
.PHONY: dtest
DOCKER_IMAGE := reg-dev
dtest: ## Run the tests in a docker container
docker build --rm --force-rm -f Dockerfile.dev -t $(DOCKER_IMAGE) .
dtest: image-dev ## Run the tests in a docker container.
docker run --rm -i $(DOCKER_FLAGS) \
-v $(CURDIR):/go/src/github.com/genuinetools/reg \
--workdir /go/src/github.com/genuinetools/reg \
-v $(CURDIR)/.certs:/etc/docker/ssl:ro \
-v /tmp:/tmp \
--net container:$(DIND_CONTAINER) \
--disable-content-trust=true \
--net container:$(NAME)-dind \
-e DOCKER_HOST=tcp://127.0.0.1:2375 \
-e DOCKER_TLS_VERIFY=true \
-e DOCKER_CERT_PATH=/etc/docker/ssl \
-e DOCKER_API_VERSION \
$(DOCKER_IMAGE) \
make test cover
$(REGISTRY)/$(NAME):dev \
make test
.PHONY: help
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: snakeoil
snakeoil: ## Update snakeoil certs for testing.
go run /usr/local/go/src/crypto/tls/generate_cert.go --host localhost,127.0.0.1 --ca
mv $(CURDIR)/key.pem $(CURDIR)/testutils/snakeoil/key.pem
mv $(CURDIR)/cert.pem $(CURDIR)/testutils/snakeoil/cert.pem

View file

@ -1,84 +1,83 @@
# reg
[![Travis CI](https://travis-ci.org/genuinetools/reg.svg?branch=master)](https://travis-ci.org/genuinetools/reg)
[![Travis CI](https://img.shields.io/travis/genuinetools/reg.svg?style=for-the-badge)](https://travis-ci.org/genuinetools/reg)
[![GoDoc](https://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://godoc.org/github.com/genuinetools/reg)
[![Github All Releases](https://img.shields.io/github/downloads/genuinetools/reg/total.svg?style=for-the-badge)](https://github.com/genuinetools/reg/releases)
Docker registry v2 command line client.
Docker registry v2 command line client and repo listing generator with security checks.
- [Installation](#installation)
- [Usage](#usage)
- [Auth](#auth)
- [List Repositories and Tags](#list-repositories-and-tags)
- [Get a Manifest](#get-a-manifest)
- [Download a Layer](#download-a-layer)
- [Delete an Image](#delete-an-image)
- [Vulnerability Reports](#vulnerability-reports)
- [Testing](#testing)
* [Installation](README.md#installation)
* [Binaries](README.md#binaries)
* [Via Go](README.md#via-go)
* [Usage](README.md#usage)
* [Auth](README.md#auth)
* [List Repositories and Tags](README.md#list-repositories-and-tags)
* [Get a Manifest](README.md#get-a-manifest)
* [Get the Digest](README.md#get-the-digest)
* [Download a Layer](README.md#download-a-layer)
* [Delete an Image](README.md#delete-an-image)
* [Vulnerability Reports](README.md#vulnerability-reports)
* [Running a Static UI Server for a Registry](README.md#running-a-static-ui-server-for-a-registry)
* [Using Self-Signed Certs with a Registry](README.md#using-self-signed-certs-with-a-registry)
* [Contributing](README.md#contributing)
## Installation
#### Binaries
- **darwin** [386](https://github.com/genuinetools/reg/releases/download/v0.13.0/reg-darwin-386) / [amd64](https://github.com/genuinetools/reg/releases/download/v0.13.0/reg-darwin-amd64)
- **linux** [386](https://github.com/genuinetools/reg/releases/download/v0.13.0/reg-linux-386) / [amd64](https://github.com/genuinetools/reg/releases/download/v0.13.0/reg-linux-amd64) / [arm](https://github.com/genuinetools/reg/releases/download/v0.13.0/reg-linux-arm) / [arm64](https://github.com/genuinetools/reg/releases/download/v0.13.0/reg-linux-arm64)
- **windows** [386](https://github.com/genuinetools/reg/releases/download/v0.13.0/reg-windows-386) / [amd64](https://github.com/genuinetools/reg/releases/download/v0.13.0/reg-windows-amd64)
For installation instructions from binaries please visit the [Releases Page](https://github.com/genuinetools/reg/releases).
#### Via Go
```bash
```console
$ go get github.com/genuinetools/reg
```
## Usage
```console
$ reg
NAME:
reg - Docker registry v2 client.
$ reg -h
reg - Docker registry v2 client.
USAGE:
reg [global options] command [command options] [arguments...]
Usage: reg <command>
VERSION:
version v0.13.0, build 3b7dafb
Flags:
AUTHOR:
The Genuinetools Authors <no-reply@butts.com>
-d enable debug logging (default: false)
-f, --force-non-ssl force allow use of non-ssl (default: false)
-k, --insecure do not verify tls certificates (default: false)
-p, --password password for the registry (default: <none>)
--skip-ping skip pinging the registry while establishing connection (default: false)
--timeout timeout for HTTP requests (default: 1m0s)
-u, --username username for the registry (default: <none>)
COMMANDS:
delete, rm delete a specific reference of a repository
layer, download download a layer for the specific reference of a repository
list, ls list all repositories
manifest get the json manifest for the specific reference of a repository
tags get the tags for a repository
vulns get a vulnerability report for the image from CoreOS Clair
help, h Shows a list of commands or help for one command
Commands:
GLOBAL OPTIONS:
--debug, -d run in debug mode
--insecure, -k do not verify tls certificates
--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]
--help, -h show help
--version, -v print the version
digest Get the digest for a repository.
layer Download a layer for a repository.
ls List all repositories.
manifest Get the json manifest for a repository.
rm Delete a specific reference of a repository.
server Run a static UI server for a registry.
tags Get the tags for a repository.
vulns Get a vulnerability report for a repository from a CoreOS Clair server.
version Show the version information.
```
Note that the `--registry` can be set by an environment variable `REG_REGISTRY`, so you can set this in your shell login scripts.
Specifying the registry on the command-line will override an environment variable setting.
**NOTE:** Be aware that `reg ls` doesn't work with `hub.docker.com` as it has a different API than the [OSS Docker Registry](https://github.com/docker/distribution).
## Auth
### Auth
`reg` will automatically try to parse your docker config credentials, but if
not present, you can pass through flags directly.
## List Repositories and Tags
### List Repositories and Tags
**Repositories**
```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
@ -91,17 +90,31 @@ chrome beta, latest, stable
**Tags**
```console
$ reg tags tor-browser
$ reg tags r.j3ss.co/tor-browser
alpha
hardened
latest
stable
# or for an offical image
$ reg tags debian
6
6.0
6.0.10
6.0.8
6.0.9
7
7-slim
7.10
7.11
7.11-slim
...
```
## Get a Manifest
### Get a Manifest
```console
$ reg manifest htop
$ reg manifest r.j3ss.co/htop
{
"schemaVersion": 1,
"name": "htop",
@ -119,26 +132,32 @@ $ reg manifest htop
}
```
## Download a Layer
### Get the Digest
```console
$ 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
### Delete an Image
```console
$ reg rm chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
$ reg rm r.j3ss.co/chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
Deleted chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
```
## Vulnerability Reports
### 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]
@ -175,7 +194,75 @@ Medium: 3
High: 1
```
## Testing
### Generating Static Website for a Registry
`reg` bundles a HTTP server that periodically generates a static website
with a list of registry images and serves it to the web.
It will run vulnerability scanning if you
have a [CoreOS Clair](https://github.com/coreos/clair) server set up
and pass the url with the `--clair` flag.
It is possible to run `reg server` just as a one time static generator.
`--once` flag makes the `server` command exit after it builds the HTML listing.
There is a demo at [r.j3ss.co](https://r.j3ss.co).
**Usage:**
```console
$ reg server -h
Usage: reg server [OPTIONS]
Run a static UI server for a registry.
Flags:
-u, --username username for the registry (default: <none>)
--listen-address address to listen on (default: <none>)
--asset-path Path to assets and templates (default: <none>)
-f, --force-non-ssl force allow use of non-ssl (default: false)
--once generate the templates once and then exit (default: false)
--skip-ping skip pinging the registry while establishing connection (default: false)
--timeout timeout for HTTP requests (default: 1m0s)
--cert path to ssl cert (default: <none>)
-d enable debug logging (default: false)
--key path to ssl key (default: <none>)
--port port for server to run on (default: 8080)
-r, --registry URL to the private registry (ex. r.j3ss.co) (default: <none>)
--clair url to clair instance (default: <none>)
-k, --insecure do not verify tls certificates (default: false)
--interval interval to generate new index.html's at (default: 1h0m0s)
-p, --password password for the registry (default: <none>)
```
**Screenshots:**
![home.png](server/home.png)
![vuln.png](server/vuln.png)
### Using Self-Signed Certs with a Registry
We do not allow users to pass all the custom certificate flags on commands
because it is unnecessarily messy and can be handled through Linux itself.
Which we believe is a better user experience than having to pass three
different flags just to communicate with a registry using self-signed or
private certificates.
Below are instructions on adding a self-signed or private certificate to your
trusted ca-certificates on Linux.
Make sure you have the package `ca-certificates` installed.
Copy the public half of your CA certificate (the one used to sign the CSR) into
the CA certificate directory (as root):
```console
$ cp cacert.pem /usr/share/ca-certificates
```
## Contributing
If you plan on contributing you should be able to run the tests locally. The
tests run for CI via docker-in-docker. But running locally with `go test`, you
@ -186,5 +273,7 @@ Add the flag `--insecure-registry localhost:5000` to your docker daemon,
documented [here](https://docs.docker.com/registry/insecure/) for testing
against an insecure registry.
OR run `make dind dtest` to avoid having to change your local docker config and
**OR**
Run `make dind dtest` to avoid having to change your local docker config and
to run the tests as docker-in-docker.

View file

@ -1 +1 @@
v0.13.0
v0.15.8

172
vendor/github.com/genuinetools/reg/basic.mk generated vendored Normal file
View file

@ -0,0 +1,172 @@
# Set an output prefix, which is the local directory if not specified
PREFIX?=$(shell pwd)
# Set the build dir, where built cross-compiled binaries will be output
BUILDDIR := ${PREFIX}/cross
# Populate version variables
# Add to compile time flags
VERSION := $(shell cat VERSION.txt)
GITCOMMIT := $(shell git rev-parse --short HEAD)
GITUNTRACKEDCHANGES := $(shell git status --porcelain --untracked-files=no)
ifneq ($(GITUNTRACKEDCHANGES),)
GITCOMMIT := $(GITCOMMIT)-dirty
endif
ifeq ($(GITCOMMIT),)
GITCOMMIT := ${GITHUB_SHA}
endif
CTIMEVAR=-X $(PKG)/version.GITCOMMIT=$(GITCOMMIT) -X $(PKG)/version.VERSION=$(VERSION)
GO_LDFLAGS=-ldflags "-w $(CTIMEVAR)"
GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static"
# Set our default go compiler
GO := go
# List the GOOS and GOARCH to build
GOOSARCHES = $(shell cat .goosarch)
# Set the graph driver as the current graphdriver if not set.
DOCKER_GRAPHDRIVER := $(if $(DOCKER_GRAPHDRIVER),$(DOCKER_GRAPHDRIVER),$(shell docker info 2>&1 | grep "Storage Driver" | sed 's/.*: //'))
export DOCKER_GRAPHDRIVER
# If this session isn't interactive, then we don't want to allocate a
# TTY, which would fail, but if it is interactive, we do want to attach
# so that the user can send e.g. ^C through.
INTERACTIVE := $(shell [ -t 0 ] && echo 1 || echo 0)
ifeq ($(INTERACTIVE), 1)
DOCKER_FLAGS += -t
endif
.PHONY: build
build: prebuild $(NAME) ## Builds a dynamic executable or package.
$(NAME): $(wildcard *.go) $(wildcard */*.go) VERSION.txt
@echo "+ $@"
$(GO) build -tags "$(BUILDTAGS)" ${GO_LDFLAGS} -o $(NAME) .
.PHONY: static
static: prebuild ## Builds a static executable.
@echo "+ $@"
CGO_ENABLED=$(CGO_ENABLED) $(GO) build \
-tags "$(BUILDTAGS) static_build" \
${GO_LDFLAGS_STATIC} -o $(NAME) .
all: clean build fmt lint test staticcheck vet install ## Runs a clean, build, fmt, lint, test, staticcheck, vet and install.
.PHONY: fmt
fmt: ## Verifies all files have been `gofmt`ed.
@echo "+ $@"
@gofmt -s -l . | grep -v '.pb.go:' | grep -v vendor | tee /dev/stderr
.PHONY: lint
lint: ## Verifies `golint` passes.
@echo "+ $@"
@golint ./... | grep -v '.pb.go:' | grep -v vendor | tee /dev/stderr
.PHONY: test
test: prebuild ## Runs the go tests.
@echo "+ $@"
@$(GO) test -v -tags "$(BUILDTAGS) cgo" $(shell $(GO) list ./... | grep -v vendor)
.PHONY: vet
vet: ## Verifies `go vet` passes.
@echo "+ $@"
@$(GO) vet $(shell $(GO) list ./... | grep -v vendor) | grep -v '.pb.go:' | tee /dev/stderr
.PHONY: staticcheck
staticcheck: ## Verifies `staticcheck` passes.
@echo "+ $@"
@staticcheck $(shell $(GO) list ./... | grep -v vendor) | grep -v '.pb.go:' | tee /dev/stderr
.PHONY: cover
cover: prebuild ## Runs go test with coverage.
@echo "" > coverage.txt
@for d in $(shell $(GO) list ./... | grep -v vendor); do \
$(GO) test -race -coverprofile=profile.out -covermode=atomic "$$d"; \
if [ -f profile.out ]; then \
cat profile.out >> coverage.txt; \
rm profile.out; \
fi; \
done;
.PHONY: install
install: prebuild ## Installs the executable or package.
@echo "+ $@"
$(GO) install -a -tags "$(BUILDTAGS)" ${GO_LDFLAGS} .
define buildpretty
mkdir -p $(BUILDDIR)/$(1)/$(2);
GOOS=$(1) GOARCH=$(2) CGO_ENABLED=$(CGO_ENABLED) $(GO) build \
-o $(BUILDDIR)/$(1)/$(2)/$(NAME) \
-a -tags "$(BUILDTAGS) static_build netgo" \
-installsuffix netgo ${GO_LDFLAGS_STATIC} .;
md5sum $(BUILDDIR)/$(1)/$(2)/$(NAME) > $(BUILDDIR)/$(1)/$(2)/$(NAME).md5;
sha256sum $(BUILDDIR)/$(1)/$(2)/$(NAME) > $(BUILDDIR)/$(1)/$(2)/$(NAME).sha256;
endef
.PHONY: cross
cross: *.go VERSION.txt prebuild ## Builds the cross-compiled binaries, creating a clean directory structure (eg. GOOS/GOARCH/binary).
@echo "+ $@"
$(foreach GOOSARCH,$(GOOSARCHES), $(call buildpretty,$(subst /,,$(dir $(GOOSARCH))),$(notdir $(GOOSARCH))))
define buildrelease
GOOS=$(1) GOARCH=$(2) CGO_ENABLED=$(CGO_ENABLED) $(GO) build \
-o $(BUILDDIR)/$(NAME)-$(1)-$(2) \
-a -tags "$(BUILDTAGS) static_build netgo" \
-installsuffix netgo ${GO_LDFLAGS_STATIC} .;
md5sum $(BUILDDIR)/$(NAME)-$(1)-$(2) > $(BUILDDIR)/$(NAME)-$(1)-$(2).md5;
sha256sum $(BUILDDIR)/$(NAME)-$(1)-$(2) > $(BUILDDIR)/$(NAME)-$(1)-$(2).sha256;
endef
.PHONY: release
release: *.go VERSION.txt prebuild ## Builds the cross-compiled binaries, naming them in such a way for release (eg. binary-GOOS-GOARCH).
@echo "+ $@"
$(foreach GOOSARCH,$(GOOSARCHES), $(call buildrelease,$(subst /,,$(dir $(GOOSARCH))),$(notdir $(GOOSARCH))))
.PHONY: bump-version
BUMP := patch
bump-version: ## Bump the version in the version file. Set BUMP to [ patch | major | minor ].
@$(GO) get -u github.com/jessfraz/junk/sembump # update sembump tool
$(eval NEW_VERSION = $(shell sembump --kind $(BUMP) $(VERSION)))
@echo "Bumping VERSION.txt from $(VERSION) to $(NEW_VERSION)"
echo $(NEW_VERSION) > VERSION.txt
@echo "Updating links to download binaries in README.md"
sed -i s/$(VERSION)/$(NEW_VERSION)/g README.md
git add VERSION.txt README.md
git commit -vsam "Bump version to $(NEW_VERSION)"
@echo "Run make tag to create and push the tag for new version $(NEW_VERSION)"
.PHONY: tag
tag: ## Create a new git tag to prepare to build a release.
git tag -sa $(VERSION) -m "$(VERSION)"
@echo "Run git push origin $(VERSION) to push your new tag to GitHub and trigger a travis build."
REGISTRY := r.j3ss.co
.PHONY: image
image: ## Create the docker image from the Dockerfile.
@docker build --rm --force-rm -t $(REGISTRY)/$(NAME) .
.PHONY: AUTHORS
AUTHORS:
@$(file >$@,# This file lists all individuals having contributed content to the repository.)
@$(file >>$@,# For how it is generated, see `make AUTHORS`.)
@echo "$(shell git log --format='\n%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf)" >> $@
.PHONY: vendor
vendor: ## Updates the vendoring directory.
@$(RM) Gopkg.toml Gopkg.lock
@$(RM) go.mod go.sum
@$(RM) -r vendor
@GO111MODULE=on $(GO) mod init
@GO111MODULE=on $(GO) mod tidy
@GO111MODULE=on $(GO) mod vendor
.PHONY: clean
clean: ## Cleanup any build binaries or packages.
@echo "+ $@"
$(RM) $(NAME)
$(RM) -r $(BUILDDIR)
.PHONY: help
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | sed 's/^[^:]*://g' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

71
vendor/github.com/genuinetools/reg/clair/ancestry.go generated vendored Normal file
View file

@ -0,0 +1,71 @@
package clair
import (
"context"
"errors"
"github.com/coreos/clair/api/v3/clairpb"
)
var (
// ErrNilGRPCConn holds the error for when the grpc connection is nil.
ErrNilGRPCConn = errors.New("grpcConn cannot be nil")
)
// GetAncestry displays an ancestry and all of its features and vulnerabilities.
func (c *Clair) GetAncestry(name string) (*clairpb.GetAncestryResponse_Ancestry, error) {
c.Logf("clair.ancestry.get name=%s", name)
if c.grpcConn == nil {
return nil, ErrNilGRPCConn
}
client := clairpb.NewAncestryServiceClient(c.grpcConn)
resp, err := client.GetAncestry(context.Background(), &clairpb.GetAncestryRequest{
AncestryName: name,
})
if err != nil {
return nil, err
}
if resp == nil {
return nil, errors.New("ancestry response was nil")
}
if resp.GetStatus() != nil {
c.Logf("clair.ancestry.get ClairStatus=%#v", *resp.GetStatus())
}
return resp.GetAncestry(), nil
}
// PostAncestry performs the analysis of all layers from the provided path.
func (c *Clair) PostAncestry(name string, layers []*clairpb.PostAncestryRequest_PostLayer) error {
c.Logf("clair.ancestry.post name=%s", name)
if c.grpcConn == nil {
return ErrNilGRPCConn
}
client := clairpb.NewAncestryServiceClient(c.grpcConn)
resp, err := client.PostAncestry(context.Background(), &clairpb.PostAncestryRequest{
AncestryName: name,
Layers: layers,
Format: "Docker",
})
if err != nil {
return err
}
if resp == nil {
return errors.New("ancestry response was nil")
}
if resp.GetStatus() != nil {
c.Logf("clair.ancestry.post ClairStatus=%#v", *resp.GetStatus())
}
return nil
}

105
vendor/github.com/genuinetools/reg/clair/clair.go generated vendored Normal file
View file

@ -0,0 +1,105 @@
package clair
import (
"crypto/tls"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"google.golang.org/grpc"
)
// Clair defines the client for retriving information from the clair API.
type Clair struct {
URL string
Client *http.Client
Logf LogfCallback
grpcConn *grpc.ClientConn
}
// LogfCallback is the callback for formatting logs.
type LogfCallback func(format string, args ...interface{})
// Quiet discards logs silently.
func Quiet(format string, args ...interface{}) {}
// Log passes log messages to the logging package.
func Log(format string, args ...interface{}) {
log.Printf(format, args...)
}
// Opt holds the options for a new clair client.
type Opt struct {
Debug bool
Insecure bool
Timeout time.Duration
}
// New creates a new Clair struct with the given URL and credentials.
func New(url string, opt Opt) (*Clair, error) {
transport := http.DefaultTransport
grpcOpt := []grpc.DialOption{}
if opt.Insecure {
transport = &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
grpcOpt = append(grpcOpt, grpc.WithInsecure())
}
errorTransport := &ErrorTransport{
Transport: transport,
}
// set the logging
logf := Quiet
if opt.Debug {
logf = Log
}
conn, err := grpc.Dial(url, grpcOpt...)
if err != nil {
logf("grpc dial %s failed: %v", url, err)
}
registry := &Clair{
URL: url,
Client: &http.Client{
Timeout: opt.Timeout,
Transport: errorTransport,
},
Logf: logf,
grpcConn: conn,
}
return registry, nil
}
// url returns a clair URL with the passed arguements concatenated.
func (c *Clair) url(pathTemplate string, args ...interface{}) string {
pathSuffix := fmt.Sprintf(pathTemplate, args...)
url := fmt.Sprintf("%s%s", c.URL, pathSuffix)
return url
}
func (c *Clair) getJSON(url string, response interface{}) (http.Header, error) {
resp, err := c.Client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
c.Logf("clair.clair resp.Status=%s", resp.Status)
if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
c.Logf("clair.clair resp.Status=%s, body=%s", resp.Status, response)
return nil, err
}
return resp.Header, nil
}

View file

@ -0,0 +1,46 @@
package clair
import (
"fmt"
"io/ioutil"
"net/http"
)
type httpStatusError struct {
Response *http.Response
Body []byte // Copied from `Response.Body` to avoid problems with unclosed bodies later. Nobody calls `err.Response.Body.Close()`, ever.
}
func (err *httpStatusError) Error() string {
return fmt.Sprintf("http: non-successful response (status=%v body=%q)", err.Response.StatusCode, err.Body)
}
var _ error = &httpStatusError{}
// ErrorTransport defines the data structure for returning errors from the round tripper.
type ErrorTransport struct {
Transport http.RoundTripper
}
// RoundTrip defines the round tripper for the error transport.
func (t *ErrorTransport) RoundTrip(request *http.Request) (*http.Response, error) {
resp, err := t.Transport.RoundTrip(request)
if err != nil {
return resp, err
}
if resp.StatusCode >= 500 {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("http: failed to read response body (status=%v, err=%q)", resp.StatusCode, err)
}
return nil, &httpStatusError{
Response: resp,
Body: body,
}
}
return resp, err
}

81
vendor/github.com/genuinetools/reg/clair/layer.go generated vendored Normal file
View file

@ -0,0 +1,81 @@
package clair
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
// GetLayer displays a Layer and optionally all of its features and vulnerabilities.
func (c *Clair) GetLayer(name string, features, vulnerabilities bool) (*Layer, error) {
url := c.url("/v1/layers/%s?features=%t&vulnerabilities=%t", name, features, vulnerabilities)
c.Logf("clair.layers.get url=%s name=%s", url, name)
var respLayer layerEnvelope
if _, err := c.getJSON(url, &respLayer); err != nil {
return nil, err
}
if respLayer.Error != nil {
return nil, fmt.Errorf("clair error: %s", respLayer.Error.Message)
}
return respLayer.Layer, nil
}
// PostLayer performs the analysis of a Layer from the provided path.
func (c *Clair) PostLayer(layer *Layer) (*Layer, error) {
url := c.url("/v1/layers")
c.Logf("clair.layers.post url=%s name=%s", url, layer.Name)
b, err := json.Marshal(layerEnvelope{Layer: layer})
if err != nil {
return nil, err
}
c.Logf("clair.layers.post req.Body=%s", string(b))
resp, err := c.Client.Post(url, "application/json", bytes.NewReader(b))
if err != nil {
return nil, err
}
defer resp.Body.Close()
c.Logf("clair.layers.post resp.Status=%s", resp.Status)
var respLayer layerEnvelope
if err := json.NewDecoder(resp.Body).Decode(&respLayer); err != nil {
return nil, err
}
if respLayer.Error != nil {
return nil, fmt.Errorf("clair error: %s", respLayer.Error.Message)
}
return respLayer.Layer, err
}
// DeleteLayer removes a layer reference from clair.
func (c *Clair) DeleteLayer(name string) error {
url := c.url("/v1/layers/%s", name)
c.Logf("clair.layers.delete url=%s name=%s", url, name)
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return err
}
resp, err := c.Client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
c.Logf("clair.clair resp.Status=%s", resp.Status)
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusNotFound {
return nil
}
return fmt.Errorf("Got status code: %d", resp.StatusCode)
}

95
vendor/github.com/genuinetools/reg/clair/layerutil.go generated vendored Normal file
View file

@ -0,0 +1,95 @@
package clair
import (
"fmt"
"strings"
"github.com/coreos/clair/api/v3/clairpb"
"github.com/docker/distribution"
"github.com/genuinetools/reg/registry"
)
// NewClairLayer will form a layer struct required for a clair scan.
func (c *Clair) NewClairLayer(r *registry.Registry, image string, fsLayers map[int]distribution.Descriptor, index int) (*Layer, error) {
var parentName string
if index < len(fsLayers)-1 {
parentName = fsLayers[index+1].Digest.String()
}
// Form the path.
p := strings.Join([]string{r.URL, "v2", image, "blobs", fsLayers[index].Digest.String()}, "/")
// Get the headers.
h, err := r.Headers(p)
if err != nil {
return nil, err
}
return &Layer{
Name: fsLayers[index].Digest.String(),
Path: p,
ParentName: parentName,
Format: "Docker",
Headers: h,
}, nil
}
// NewClairV3Layer will form a layer struct required for a clair scan.
func (c *Clair) NewClairV3Layer(r *registry.Registry, image string, fsLayer distribution.Descriptor) (*clairpb.PostAncestryRequest_PostLayer, error) {
// Form the path.
p := strings.Join([]string{r.URL, "v2", image, "blobs", fsLayer.Digest.String()}, "/")
// Get the headers.
h, err := r.Headers(p)
if err != nil {
return nil, err
}
return &clairpb.PostAncestryRequest_PostLayer{
Hash: fsLayer.Digest.String(),
Path: p,
Headers: h,
}, nil
}
func (c *Clair) getLayers(r *registry.Registry, repo, tag string, filterEmpty bool) (map[int]distribution.Descriptor, error) {
ok := true
// Get the manifest to pass to clair.
mf, err := r.ManifestV2(repo, tag)
if err != nil {
ok = false
c.Logf("couldn't retrieve manifest v2, falling back to v1")
}
filteredLayers := map[int]distribution.Descriptor{}
// Filter out the empty layers.
if ok {
for i := 0; i < len(mf.Layers); i++ {
if filterEmpty && IsEmptyLayer(mf.Layers[i].Digest) {
continue
} else {
filteredLayers[len(mf.Layers)-i-1] = mf.Layers[i]
}
}
return filteredLayers, nil
}
m, err := r.ManifestV1(repo, tag)
if err != nil {
return nil, fmt.Errorf("getting the v1 manifest for %s:%s failed: %v", repo, tag, err)
}
for i := 0; i < len(m.FSLayers); i++ {
if filterEmpty && IsEmptyLayer(m.FSLayers[i].BlobSum) {
continue
} else {
filteredLayers[i] = distribution.Descriptor{
Digest: m.FSLayers[i].BlobSum,
}
}
}
return filteredLayers, nil
}

77
vendor/github.com/genuinetools/reg/clair/types.go generated vendored Normal file
View file

@ -0,0 +1,77 @@
package clair
import "github.com/opencontainers/go-digest"
const (
// EmptyLayerBlobSum is the blob sum of empty layers.
EmptyLayerBlobSum = "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
// LegacyEmptyLayerBlobSum is the blob sum of empty layers used by docker
// before it could support a truly empty layer.
LegacyEmptyLayerBlobSum = "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
)
// IsEmptyLayer determines whether the blob sum is one of the known empty
// layers.
func IsEmptyLayer(blobSum digest.Digest) bool {
return blobSum == EmptyLayerBlobSum || blobSum == LegacyEmptyLayerBlobSum
}
var (
// Priorities are the vulnerability priority labels.
Priorities = []string{"Unknown", "Negligible", "Low", "Medium", "High", "Critical", "Defcon1", "Fixable"}
)
// Error describes the structure of a clair error.
type Error struct {
Message string `json:"Message,omitempty"`
}
// Layer represents an image layer.
type Layer struct {
Name string `json:"Name,omitempty"`
NamespaceName string `json:"NamespaceName,omitempty"`
Path string `json:"Path,omitempty"`
Headers map[string]string `json:"Headers,omitempty"`
ParentName string `json:"ParentName,omitempty"`
Format string `json:"Format,omitempty"`
IndexedByVersion int `json:"IndexedByVersion,omitempty"`
Features []feature `json:"Features,omitempty"`
}
type layerEnvelope struct {
Layer *Layer `json:"Layer,omitempty"`
Error *Error `json:"Error,omitempty"`
}
// Vulnerability represents vulnerability entity returned by Clair.
type Vulnerability struct {
Name string `json:"Name,omitempty"`
NamespaceName string `json:"NamespaceName,omitempty"`
Description string `json:"Description,omitempty"`
Link string `json:"Link,omitempty"`
Severity string `json:"Severity,omitempty"`
Metadata map[string]interface{} `json:"Metadata,omitempty"`
FixedBy string `json:"FixedBy,omitempty"`
FixedIn []feature `json:"FixedIn,omitempty"`
}
// VulnerabilityReport represents the result of a vulnerability scan of a repo.
type VulnerabilityReport struct {
Name string
RegistryURL string
Repo string
Tag string
Date string
Vulns []Vulnerability
VulnsBySeverity map[string][]Vulnerability
BadVulns int
}
type feature struct {
Name string `json:"Name,omitempty"`
NamespaceName string `json:"NamespaceName,omitempty"`
VersionFormat string `json:"VersionFormat,omitempty"`
Version string `json:"Version,omitempty"`
Vulnerabilities []Vulnerability `json:"Vulnerabilities,omitempty"`
AddedBy string `json:"AddedBy,omitempty"`
}

163
vendor/github.com/genuinetools/reg/clair/vulns.go generated vendored Normal file
View file

@ -0,0 +1,163 @@
package clair
import (
"errors"
"fmt"
"time"
"github.com/coreos/clair/api/v3/clairpb"
"github.com/genuinetools/reg/registry"
)
// Vulnerabilities scans the given repo and tag.
func (c *Clair) Vulnerabilities(r *registry.Registry, repo, tag string) (VulnerabilityReport, error) {
report := VulnerabilityReport{
RegistryURL: r.Domain,
Repo: repo,
Tag: tag,
Date: time.Now().Local().Format(time.RFC1123),
VulnsBySeverity: make(map[string][]Vulnerability),
}
filteredLayers, err := c.getLayers(r, repo, tag, true)
if err != nil {
return report, fmt.Errorf("getting filtered layers failed: %v", err)
}
if len(filteredLayers) == 0 {
fmt.Printf("No need to analyse image %s:%s as there is no non-emtpy layer", repo, tag)
return report, nil
}
for i := len(filteredLayers) - 1; i >= 0; i-- {
// Form the clair layer.
l, err := c.NewClairLayer(r, repo, filteredLayers, i)
if err != nil {
return report, err
}
// Post the layer.
if _, err := c.PostLayer(l); err != nil {
return report, err
}
}
report.Name = filteredLayers[0].Digest.String()
vl, err := c.GetLayer(filteredLayers[0].Digest.String(), true, true)
if err != nil {
return report, err
}
// Get the vulns.
for _, f := range vl.Features {
report.Vulns = append(report.Vulns, f.Vulnerabilities...)
}
vulnsBy := func(sev string, store map[string][]Vulnerability) []Vulnerability {
items, found := store[sev]
if !found {
items = make([]Vulnerability, 0)
store[sev] = items
}
return items
}
// group by severity
for _, v := range report.Vulns {
sevRow := vulnsBy(v.Severity, report.VulnsBySeverity)
report.VulnsBySeverity[v.Severity] = append(sevRow, v)
}
// calculate number of bad vulns
report.BadVulns = len(report.VulnsBySeverity["High"]) + len(report.VulnsBySeverity["Critical"]) + len(report.VulnsBySeverity["Defcon1"])
return report, nil
}
// VulnerabilitiesV3 scans the given repo and tag using the clair v3 API.
func (c *Clair) VulnerabilitiesV3(r *registry.Registry, repo, tag string) (VulnerabilityReport, error) {
report := VulnerabilityReport{
RegistryURL: r.Domain,
Repo: repo,
Tag: tag,
Date: time.Now().Local().Format(time.RFC1123),
VulnsBySeverity: make(map[string][]Vulnerability),
}
layers, err := c.getLayers(r, repo, tag, false)
if err != nil {
return report, fmt.Errorf("getting filtered layers failed: %v", err)
}
if len(layers) == 0 {
fmt.Printf("No need to analyse image %s:%s as there is no non-empty layer", repo, tag)
return report, nil
}
report.Name = layers[0].Digest.String()
clairLayers := []*clairpb.PostAncestryRequest_PostLayer{}
for i := len(layers) - 1; i >= 0; i-- {
// Form the clair layer.
l, err := c.NewClairV3Layer(r, repo, layers[i])
if err != nil {
return report, err
}
// Append the layer.
clairLayers = append(clairLayers, l)
}
// Post the ancestry.
if err := c.PostAncestry(layers[0].Digest.String(), clairLayers); err != nil {
return report, fmt.Errorf("posting ancestry failed: %v", err)
}
// Get the ancestry.
vl, err := c.GetAncestry(layers[0].Digest.String())
if err != nil {
return report, err
}
if vl == nil {
return report, errors.New("ancestry response was nil")
}
// Get the vulns.
for _, l := range vl.GetLayers() {
for _, f := range l.GetDetectedFeatures() {
for _, v := range f.GetVulnerabilities() {
report.Vulns = append(report.Vulns, Vulnerability{
Name: v.Name,
NamespaceName: v.NamespaceName,
Description: v.Description,
Link: v.Link,
Severity: v.Severity,
Metadata: map[string]interface{}{v.Metadata: ""},
FixedBy: v.FixedBy,
})
}
}
}
vulnsBy := func(sev string, store map[string][]Vulnerability) []Vulnerability {
items, found := store[sev]
if !found {
items = make([]Vulnerability, 0)
store[sev] = items
}
return items
}
// Group by severity.
for _, v := range report.Vulns {
sevRow := vulnsBy(v.Severity, report.VulnsBySeverity)
report.VulnsBySeverity[v.Severity] = append(sevRow, v)
}
// calculate number of bad vulns
report.BadVulns = len(report.VulnsBySeverity["High"]) + len(report.VulnsBySeverity["Critical"]) + len(report.VulnsBySeverity["Defcon1"])
return report, nil
}

View file

@ -1,31 +0,0 @@
package main
import (
"fmt"
"github.com/genuinetools/reg/repoutils"
"github.com/urfave/cli"
)
var deleteCommand = cli.Command{
Name: "delete",
Aliases: []string{"rm"},
Usage: "delete a specific reference of a repository",
Action: func(c *cli.Context) error {
if len(c.Args()) < 1 {
return fmt.Errorf("pass the name of the repository")
}
repo, ref, err := repoutils.GetRepoAndRef(c.Args()[0])
if err != nil {
return err
}
if err := r.Delete(repo, ref); err != nil {
return fmt.Errorf("Delete %s@%s failed: %v", repo, ref, err)
}
fmt.Printf("Deleted %s@%s\n", repo, ref)
return nil
},
}

View file

@ -1,37 +0,0 @@
package main
import (
"strings"
"testing"
)
func TestDelete(t *testing.T) {
// Make sure we have 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, latest"}
for _, e := range expected {
if !strings.Contains(out, e) {
t.Logf("expected to contain: %s\ngot: %s", e, out)
}
}
// Remove busybox image.
if out, err := run("rm", "busybox"); err != nil {
t.Fatalf("output: %s, error: %v", string(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)
}
}
}

48
vendor/github.com/genuinetools/reg/digest.go generated vendored Normal file
View file

@ -0,0 +1,48 @@
package main
import (
"context"
"flag"
"fmt"
"github.com/genuinetools/reg/registry"
)
const digestHelp = `Get the digest for a repository.`
func (cmd *digestCommand) Name() string { return "digest" }
func (cmd *digestCommand) Args() string { return "[OPTIONS] NAME[:TAG]" }
func (cmd *digestCommand) ShortHelp() string { return digestHelp }
func (cmd *digestCommand) LongHelp() string { return digestHelp }
func (cmd *digestCommand) Hidden() bool { return false }
func (cmd *digestCommand) Register(fs *flag.FlagSet) {}
type digestCommand struct{}
func (cmd *digestCommand) Run(ctx context.Context, args []string) error {
if len(args) < 1 {
return fmt.Errorf("pass the name of the repository")
}
image, err := registry.ParseImage(args[0])
if err != nil {
return err
}
// Create the registry client.
r, err := createRegistryClient(image.Domain)
if err != nil {
return err
}
// Get the digest.
digest, err := r.Digest(image)
if err != nil {
return err
}
fmt.Println(digest.String())
return nil
}

52
vendor/github.com/genuinetools/reg/go.mod generated vendored Normal file
View file

@ -0,0 +1,52 @@
module github.com/genuinetools/reg
require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Microsoft/go-winio v0.4.11 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect
github.com/containerd/continuity v0.0.0-20180921161001-7f53d412b9eb // indirect
github.com/coreos/clair v0.0.0-20180919182544-44ae4bc9590a
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v0.0.0-20180920165730-54c19e67f69c // indirect
github.com/docker/distribution v0.0.0-20180920194744-16128bbac47f
github.com/docker/docker v0.0.0-20180924202107-a9c061deec0f
github.com/docker/docker-ce v0.0.0-20180924210327-f53bd8bb8e43
github.com/docker/docker-credential-helpers v0.6.1 // indirect
github.com/docker/go-connections v0.0.0-20180821093606-97c2040d34df // indirect
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 // indirect
github.com/docker/go-units v0.3.3 // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/fernet/fernet-go v0.0.0-20180830025343-9eac43b88a5e // indirect
github.com/genuinetools/pkg v0.0.0-20180910213200-1c141f661797
github.com/gogo/protobuf v1.1.1 // indirect
github.com/google/go-cmp v0.2.0
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2
github.com/grpc-ecosystem/grpc-gateway v1.5.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.0
github.com/onsi/gomega v1.4.2 // indirect
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/opencontainers/runc v0.1.1 // indirect
github.com/peterhellberg/link v1.0.0
github.com/pkg/errors v0.8.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v0.0.0-20180924113449-f69c853d21c1 // indirect
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect
github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7 // indirect
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371
github.com/sirupsen/logrus v1.0.6
github.com/stretchr/testify v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b // indirect
golang.org/x/net v0.0.0-20180925072008-f04abc6bdfa7 // indirect
golang.org/x/sys v0.0.0-20180925112736-b09afc3d579e // indirect
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
google.golang.org/genproto v0.0.0-20180924164928-221a8d4f7494 // indirect
google.golang.org/grpc v1.15.0
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
gotest.tools v2.1.0+incompatible // indirect
)

134
vendor/github.com/genuinetools/reg/go.sum generated vendored Normal file
View file

@ -0,0 +1,134 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/containerd/continuity v0.0.0-20180921161001-7f53d412b9eb h1:qSMRxG547z/BgQmyVyADxaMADQXVAD9uleP2sQeClbo=
github.com/containerd/continuity v0.0.0-20180921161001-7f53d412b9eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/coreos/clair v0.0.0-20180919182544-44ae4bc9590a h1:glxUtT0RlaVJU86kg78ygzfhwW6D+uj5H+aOK01QDgI=
github.com/coreos/clair v0.0.0-20180919182544-44ae4bc9590a/go.mod h1:uXhHPWAoRqw0jJc2f8RrPCwRhIo9otQ8OEWUFtpCiwA=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/cli v0.0.0-20180920165730-54c19e67f69c h1:QlAVcyoF7QQVN7zV+xYBjgwtRVlRU3WCTCpb2mcqQrM=
github.com/docker/cli v0.0.0-20180920165730-54c19e67f69c/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v0.0.0-20180920194744-16128bbac47f h1:hYf+mPizfvpH6VgIxdntnOmQHd1F1mQUc1oG+j3Ol2g=
github.com/docker/distribution v0.0.0-20180920194744-16128bbac47f/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v0.0.0-20180924202107-a9c061deec0f h1:BrSaWINJwChqVJoPy/AR82wCtE+Sa1MAB2UiO3n2zcY=
github.com/docker/docker v0.0.0-20180924202107-a9c061deec0f/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-ce v0.0.0-20180924210327-f53bd8bb8e43 h1:NDAvq++bKWHxKDxEclVIgl737gMim2WDYoKHFQSBXfA=
github.com/docker/docker-ce v0.0.0-20180924210327-f53bd8bb8e43/go.mod h1:l1FUGRYBvbjnZ8MS6A2xOji4aZFlY/Qmgz7p4oXH7ac=
github.com/docker/docker-credential-helpers v0.6.1 h1:Dq4iIfcM7cNtddhLVWe9h4QDjsi4OER3Z8voPu/I52g=
github.com/docker/docker-credential-helpers v0.6.1/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/go-connections v0.0.0-20180821093606-97c2040d34df h1:ADMjlaDGEn0OOQIieyxanhAt41jcngf8rf78X2eKNLw=
github.com/docker/go-connections v0.0.0-20180821093606-97c2040d34df/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 h1:yWHOI+vFjEsAakUTSrtqc/SAHrhSkmn48pqjidZX3QA=
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/fernet/fernet-go v0.0.0-20180830025343-9eac43b88a5e h1:P10tZmVD2XclAaT9l7OduMH1OLFzTa1wUuUqHZnEdI0=
github.com/fernet/fernet-go v0.0.0-20180830025343-9eac43b88a5e/go.mod h1:2H9hjfbpSMHwY503FclkV/lZTBh2YlOmLLSda12uL8c=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/genuinetools/pkg v0.0.0-20180910213200-1c141f661797 h1:SGpZXDd/CFeDIY4Rq5cFO8K/uqDblHUxjlzOmjFpvRg=
github.com/genuinetools/pkg v0.0.0-20180910213200-1c141f661797/go.mod h1:XTcrCYlXPxnxL2UpnwuRn7tcaTn9HAhxFoFJucootk8=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/grpc-ecosystem/grpc-gateway v1.5.0 h1:WcmKMm43DR7RdtlkEXQJyo5ws8iTp98CyhCCbOHMvNI=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2 h1:QhPf3A2AZW3tTGvHPg0TA+CR3oHbVLlXUhlghqISp1I=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/peterhellberg/link v1.0.0 h1:mUWkiegowUXEcmlb+ybF75Q/8D2Y0BjZtR8cxoKhaQo=
github.com/peterhellberg/link v1.0.0/go.mod h1:gtSlOT4jmkY8P47hbTc8PTgiDDWpdPbFYl75keYyBB8=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.0.0-20180924113449-f69c853d21c1 h1:mEzWvBiJdUbhqHRT6kNSGzD6IDcWCWF2uAhrEEE740M=
github.com/prometheus/client_golang v0.0.0-20180924113449-f69c853d21c1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7 h1:NgR6WN8nQ4SmFC1sSUHY8SriLuWCZ6cCIQtH4vDZN3c=
github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371 h1:SWV2fHctRpRrp49VXJ6UZja7gU9QLHwRpIPBN89SKEo=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ=
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180925072008-f04abc6bdfa7 h1:zKzVgSQ8WOSHzD7I4k8LQjrHUUCNOlBsgc0PcYLVNnY=
golang.org/x/net v0.0.0-20180925072008-f04abc6bdfa7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180925112736-b09afc3d579e h1:LSlw/Dbj0MkNvPYAAkGinYmGliq+aqS7eKPYlE4oWC4=
golang.org/x/sys v0.0.0-20180925112736-b09afc3d579e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180924164928-221a8d4f7494 h1:WIJ3k0fGJRrCVzZTuGmcBnUzWeSDpWiP+jUOxWkA8bo=
google.golang.org/genproto v0.0.0-20180924164928-221a8d4f7494/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.15.0 h1:Az/KuahOM4NAidTEuJCv/RonAA7rYsTPkqXVjr+8OOw=
google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.1.0+incompatible h1:5USw7CrJBYKqjg9R7QlA6jzqZKEAtvW82aNmsxxGPxw=
gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

317
vendor/github.com/genuinetools/reg/handlers.go generated vendored Normal file
View file

@ -0,0 +1,317 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/genuinetools/reg/clair"
"github.com/genuinetools/reg/registry"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)
type registryController struct {
reg *registry.Registry
cl *clair.Clair
interval time.Duration
l sync.Mutex
tmpl *template.Template
generateOnly bool
}
type v1Compatibility struct {
ID string `json:"id"`
Created time.Time `json:"created"`
}
// A Repository holds data after a vulnerability scan of a single repo
type Repository struct {
Name string `json:"name"`
Tag string `json:"tag"`
Created time.Time `json:"created"`
URI string `json:"uri"`
VulnerabilityReport clair.VulnerabilityReport `json:"vulnerability"`
}
// A AnalysisResult holds all vulnerabilities of a scan
type AnalysisResult struct {
Repositories []Repository `json:"repositories"`
RegistryDomain string `json:"registryDomain"`
Name string `json:"name"`
LastUpdated string `json:"lastUpdated"`
HasVulns bool `json:"hasVulns"`
UpdateInterval time.Duration
}
func (rc *registryController) repositories(staticDir string) error {
rc.l.Lock()
defer rc.l.Unlock()
logrus.Infof("fetching catalog for %s...", rc.reg.Domain)
result := AnalysisResult{
RegistryDomain: rc.reg.Domain,
LastUpdated: time.Now().Local().Format(time.RFC1123),
UpdateInterval: rc.interval,
}
repoList, err := rc.reg.Catalog("")
if err != nil {
return fmt.Errorf("getting catalog for %s failed: %v", rc.reg.Domain, err)
}
var wg sync.WaitGroup
for _, repo := range repoList {
repoURI := fmt.Sprintf("%s/%s", rc.reg.Domain, repo)
r := Repository{
Name: repo,
URI: repoURI,
}
result.Repositories = append(result.Repositories, r)
if !rc.generateOnly {
// Continue early because we don't need to generate the tags pages.
continue
}
// Generate the tags pages in a go routine.
wg.Add(1)
go func(repo string) {
defer wg.Done()
logrus.Infof("generating static tags page for repo %s", repo)
// Parse and execute the tags templates.
// If we are generating the tags files, disable vulnerability links in the
// templates since they won't go anywhere without a server side component.
b, err := rc.generateTagsTemplate(repo, false)
if err != nil {
logrus.Warnf("generating tags template for repo %q failed: %v", repo, err)
}
// Create the directory for the static tags files.
tagsDir := filepath.Join(staticDir, "repo", repo, "tags")
if err := os.MkdirAll(tagsDir, 0755); err != nil {
logrus.Warn(err)
}
// Write the tags file.
tagsFile := filepath.Join(tagsDir, "index.html")
if err := ioutil.WriteFile(tagsFile, b, 0755); err != nil {
logrus.Warnf("writing tags template for repo %s to %sfailed: %v", repo, tagsFile, err)
}
}(repo)
}
wg.Wait()
// Parse & execute the template.
logrus.Info("executing the template repositories")
// Create the static directory.
if err := os.MkdirAll(staticDir, 0755); err != nil {
return err
}
// Creating the index file.
path := filepath.Join(staticDir, "index.html")
logrus.Debugf("creating/opening file %s", path)
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
// Execute the template on the index.html file.
if err := rc.tmpl.ExecuteTemplate(f, "repositories", result); err != nil {
f.Close()
return fmt.Errorf("execute template repositories failed: %v", err)
}
return nil
}
func (rc *registryController) tagsHandler(w http.ResponseWriter, r *http.Request) {
logrus.WithFields(logrus.Fields{
"func": "tags",
"URL": r.URL,
"method": r.Method,
}).Info("fetching tags")
// Parse the query variables.
vars := mux.Vars(r)
repo, err := url.QueryUnescape(vars["repo"])
if err != nil || repo == "" {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Empty repo")
return
}
// Generate the tags template.
b, err := rc.generateTagsTemplate(repo, rc.cl != nil)
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "tags",
"URL": r.URL,
"method": r.Method,
}).Errorf("getting tags for %s failed: %v", repo, err)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Getting tags for %s failed", repo)
return
}
// Write the template.
fmt.Fprint(w, string(b))
}
func (rc *registryController) generateTagsTemplate(repo string, hasVulns bool) ([]byte, error) {
// Get the tags from the server.
tags, err := rc.reg.Tags(repo)
if err != nil {
return nil, fmt.Errorf("getting tags for %s failed: %v", repo, err)
}
// Error out if there are no tags / images
// (the above err != nil does not error out when nothing has been found)
if len(tags) == 0 {
return nil, fmt.Errorf("no tags found for repo: %s", repo)
}
result := AnalysisResult{
RegistryDomain: rc.reg.Domain,
LastUpdated: time.Now().Local().Format(time.RFC1123),
UpdateInterval: rc.interval,
Name: repo,
HasVulns: hasVulns, // if we have a clair client we can return vulns
}
for _, tag := range tags {
// get the manifest
m1, err := rc.reg.ManifestV1(repo, tag)
if err != nil {
return nil, fmt.Errorf("getting v1 manifest for %s:%s failed: %v", repo, tag, err)
}
var createdDate time.Time
for _, h := range m1.History {
var comp v1Compatibility
if err := json.Unmarshal([]byte(h.V1Compatibility), &comp); err != nil {
return nil, fmt.Errorf("unmarshal v1 manifest for %s:%s failed: %v", repo, tag, err)
}
createdDate = comp.Created
break
}
repoURI := fmt.Sprintf("%s/%s", rc.reg.Domain, repo)
if tag != "latest" {
repoURI += ":" + tag
}
rp := Repository{
Name: repo,
Tag: tag,
URI: repoURI,
Created: createdDate,
}
result.Repositories = append(result.Repositories, rp)
}
// Execute the template.
var buf bytes.Buffer
if err := rc.tmpl.ExecuteTemplate(&buf, "tags", result); err != nil {
return nil, fmt.Errorf("template rendering failed: %v", err)
}
return buf.Bytes(), nil
}
func (rc *registryController) vulnerabilitiesHandler(w http.ResponseWriter, r *http.Request) {
logrus.WithFields(logrus.Fields{
"func": "vulnerabilities",
"URL": r.URL,
"method": r.Method,
}).Info("fetching vulnerabilities")
// Parse the query variables.
vars := mux.Vars(r)
repo, err := url.QueryUnescape(vars["repo"])
tag := vars["tag"]
if err != nil || repo == "" {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Empty repo")
return
}
if tag == "" {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Empty tag")
return
}
image, err := registry.ParseImage(repo + ":" + tag)
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "vulnerabilities",
"URL": r.URL,
"method": r.Method,
}).Errorf("parsing image %s:%s failed: %v", repo, tag, err)
w.WriteHeader(http.StatusInternalServerError)
return
}
// Get the vulnerability report.
result, err := rc.cl.VulnerabilitiesV3(rc.reg, image.Path, image.Reference())
if err != nil {
// Fallback to Clair v2 API.
result, err = rc.cl.Vulnerabilities(rc.reg, image.Path, image.Reference())
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "vulnerabilities",
"URL": r.URL,
"method": r.Method,
}).Errorf("vulnerability scanning for %s:%s failed: %v", repo, tag, err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
if strings.HasSuffix(r.URL.String(), ".json") {
js, err := json.Marshal(result)
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "vulnerabilities",
"URL": r.URL,
"method": r.Method,
}).Errorf("json marshal failed: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(js)
return
}
// Execute the template.
if err := rc.tmpl.ExecuteTemplate(w, "vulns", result); err != nil {
logrus.WithFields(logrus.Fields{
"func": "vulnerabilities",
"URL": r.URL,
"method": r.Method,
}).Errorf("template rendering failed: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}

View file

@ -0,0 +1,37 @@
// +build ignore
package main
import (
"net/http"
"os"
"path/filepath"
"github.com/shurcooL/vfsgen"
"github.com/sirupsen/logrus"
)
func main() {
wd, err := os.Getwd()
if err != nil {
logrus.Fatal(err)
}
// Generate server assets.
assets := http.Dir(filepath.Join(wd, "server/static"))
if err := vfsgen.Generate(assets, vfsgen.Options{
Filename: filepath.Join(wd, "internal/binutils/static", "static.go"),
PackageName: "static",
VariableName: "Assets",
}); err != nil {
logrus.Fatal(err)
}
// Generate template assets.
assets = http.Dir(filepath.Join(wd, "server/templates"))
if err := vfsgen.Generate(assets, vfsgen.Options{
Filename: filepath.Join(wd, "internal/binutils/templates", "templates.go"),
PackageName: "templates",
VariableName: "Assets",
}); err != nil {
logrus.Fatal(err)
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,200 @@
// Code generated by vfsgen; DO NOT EDIT.
package templates
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
pathpkg "path"
"time"
)
// Assets statically implements the virtual filesystem provided to vfsgen.
var Assets = func() http.FileSystem {
fs := vfsgen۰FS{
"/": &vfsgen۰DirInfo{
name: "/",
modTime: time.Date(2018, 9, 10, 21, 40, 58, 715425786, time.UTC),
},
"/repositories.html": &vfsgen۰CompressedFileInfo{
name: "repositories.html",
modTime: time.Date(2018, 9, 10, 21, 40, 58, 715425786, time.UTC),
uncompressedSize: 1904,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x55\xc9\x6e\xe4\x36\x10\xbd\xf7\x57\x94\x89\x39\x8e\x44\x18\x39\x8c\xd3\xa0\x84\x04\x1e\x1f\x0c\x64\x19\x18\x31\x90\x60\x30\x07\x36\x55\x92\x68\x53\xa4\x86\x2c\xb5\xd1\x51\xf4\xef\x01\xb5\xa0\x57\x1b\xce\x21\xba\x50\x7c\xf5\xea\xb1\x16\xaa\xd4\xf7\x05\x96\xda\x22\x30\x8f\xad\x0b\x9a\x9c\xd7\x18\xd8\x30\xac\xc4\xd5\xe7\xdf\x6f\xff\xf8\xeb\xcb\x1d\xd4\xd4\x98\x7c\x25\xae\x92\xe4\xab\x2e\xc1\x10\xdc\xdf\xc1\xa7\x6f\x39\x8c\x8f\x88\x56\x50\x46\x86\x90\x31\xeb\x92\xa7\x00\x86\x12\x8d\x3f\x4e\xcb\xcd\xb4\x7c\x62\x39\x88\xab\xaf\x68\x0b\x5d\x7e\x4b\x92\xbd\xda\xa1\xd4\x3b\xd4\xde\x90\xb9\x79\x8f\xcc\x6b\xfe\x15\xcd\x12\x11\xc8\x2f\xf8\x8f\x8e\x49\x72\xec\x5c\xa3\x2c\xf2\xd5\x78\x60\x83\x24\x41\xd5\xd2\x07\xa4\x8c\x75\x54\x26\x37\x6c\x36\x6d\x64\x40\xa8\x3d\x96\x19\xe3\x0c\x0e\xf9\x35\x51\x9b\xe0\xf7\x4e\x6f\x33\xf6\x67\xf2\xf8\x73\x72\xeb\x9a\x56\x92\xde\x18\x64\xa0\x9c\x25\xb4\x94\xb1\xfb\xbb\x0c\x8b\x0a\x3f\xaa\xda\xbb\x06\xb3\xeb\x45\x97\x34\x19\xcc\xfb\x1e\xd2\x07\xac\x74\x20\xbf\xfb\xec\x1a\xa9\x2d\x0c\x83\xe0\x93\x71\x22\x1a\x6d\x9f\xc1\xa3\xc9\x98\x56\xce\x32\xa0\x5d\x8b\x19\xd3\x8d\xac\x90\x6b\xe5\xd8\x12\x5c\x20\x49\x5a\xf1\x52\x6e\x23\x2f\x8d\xa6\x33\x85\x40\x3b\x83\xa1\x46\xa4\x53\x37\x15\x02\x9f\xac\xa9\x0a\x81\x01\xcf\x57\x82\x4f\x15\x12\x1b\x57\xec\x66\xa9\xfa\xfa\xb5\x90\xeb\xeb\x99\x52\x3a\xdf\x4c\xaf\xe3\x56\xdb\xb6\x23\xb0\xb2\xc1\x8c\x95\xda\x10\xfa\x25\x85\x80\xd2\xab\x9a\xe5\x42\x2e\xbd\x52\x06\xa5\x67\xf9\xb8\x08\x2e\x67\x41\x3e\x29\x4e\x9b\x42\x6f\x17\xf6\x8b\x97\x6d\x8b\x9e\x1d\x1c\x46\x72\xb3\xd4\x6d\x8f\xf9\x63\x60\x02\xeb\xfc\x61\xf9\x62\x76\xf0\x9b\x6c\x50\x70\xaa\x2f\x13\xbf\x74\xc6\xc0\xad\x6b\x1a\x69\x8b\x73\x96\xe0\xa7\x07\xf4\x3d\x78\x69\x2b\x84\x0f\xcf\xb8\xfb\x08\x1f\xb6\xd2\x74\x08\xeb\x2c\x96\x6d\xff\x91\xc2\x30\xbc\x27\xce\x02\xb6\xd2\xe8\xca\x66\x8c\x5c\xcb\xce\x19\x23\x4b\x2e\xcd\x8c\x53\x80\xf7\xfd\x7c\x66\x1a\xf3\x82\x7f\xa0\xf3\xe6\x7b\x87\x7e\x07\xc3\xc0\x49\x56\xe1\x15\x99\x13\xbf\x93\xf8\xf6\xf9\xca\x0b\x71\x72\x2a\xe6\x0e\x9d\x86\x3f\x47\xef\x75\x55\x13\x03\xeb\x62\xd7\xfe\xd7\x34\x84\x72\x05\xe6\x85\x53\xcf\xe8\xa1\x8d\xbd\xdb\x2b\x3d\x3e\xdc\x8f\x97\x75\xa4\xfc\xd7\xf4\x4e\x90\xf3\xae\xa3\x2d\x0e\xab\x26\xf8\xc1\x75\x14\xbc\xd0\xdb\x0b\x97\xb8\x74\x8e\x8e\xef\x70\x9b\xff\x2a\x0b\x84\x17\x4d\xf5\x9c\x8b\xf8\x61\x8e\x18\x36\xbb\x7d\x95\xe2\xf8\x09\x6b\xce\x2b\x4d\x75\xb7\x49\x95\x6b\xf8\x13\x86\x50\x7a\xf9\x37\xcb\x7f\x5a\x5e\x63\x3e\x82\xb7\x47\xfa\xb7\x35\xaa\x67\xd7\x11\x50\x8d\x10\x5c\xe7\x15\x42\xd4\x07\x49\xeb\x37\xf5\x2b\xb4\x9d\xb6\x48\xce\x99\xc0\x3d\x56\x2c\x7f\xc3\x78\xe9\xe4\x5f\x64\x20\x78\x6c\x0b\x49\x58\xac\x63\xc9\xd2\x88\xcc\xc0\xd8\x99\x63\xfe\x64\x81\x7b\x4b\xe8\xb7\xd2\xac\xc7\x32\xa7\x13\xba\x80\x87\x6e\x53\x95\xe3\xa8\xe7\xe9\x54\xd9\x38\xe9\x47\x4b\x50\x5e\xb7\x04\xc1\xab\xfd\xd0\x7b\x0a\x7c\x82\x43\x1a\xff\x11\x62\xde\xc5\xd1\x37\xcd\x3c\xc1\xa7\xbf\x67\xdf\xa3\x2d\x86\x61\xf5\x6f\x00\x00\x00\xff\xff\x82\xfc\x97\x4b\x70\x07\x00\x00"),
},
"/tags.html": &vfsgen۰CompressedFileInfo{
name: "tags.html",
modTime: time.Date(2018, 9, 10, 21, 40, 58, 715425786, time.UTC),
uncompressedSize: 2868,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x56\xdf\x6f\xdb\x36\x10\x7e\xf7\x5f\x71\x25\x0a\xb4\x05\x2c\x31\xe9\xd6\xb5\x73\x24\x61\x85\x9b\x61\x1d\xf6\x0b\x43\x32\x6c\x08\xf2\x40\x4b\x27\x89\x0e\x4d\xaa\xe4\xc9\x89\xa7\xe9\x7f\x1f\x68\xc9\x8e\xec\xd8\x41\x86\xee\xa1\x7e\x11\x7d\x3f\x3e\x7d\xf7\x9d\xc8\x63\xd3\x64\x98\x4b\x8d\xc0\x48\x14\x8e\xb5\xed\x28\x7a\xf6\xe1\xd7\xe9\xc5\x5f\xbf\x9d\x43\x49\x0b\x95\x8c\xa2\x67\x41\x70\x25\x73\x50\x04\x1f\xcf\xe1\xed\x75\x02\xeb\x5f\xe4\xbd\x90\x2a\xe1\x5c\xcc\xb4\x09\xe6\x0e\x14\x05\x12\xbf\xed\x1e\xef\xba\xc7\x5b\x96\x40\xf4\xec\x0a\x75\x26\xf3\xeb\x20\xb8\x47\x1b\x42\x3d\x01\xed\x11\x98\x77\x4f\x81\x39\x96\x5f\x50\x0f\xe1\x0d\xc9\x81\xfc\x75\x62\x10\xec\x26\x97\x28\xb2\x64\xb4\x7e\xe1\x02\x49\x40\x5a\x0a\xeb\x90\x62\x56\x53\x1e\xbc\x63\xbd\x6b\x26\x1c\x42\x69\x31\x8f\x19\x67\x30\x8c\x2f\x89\xaa\x00\x3f\xd5\x72\x19\xb3\x3f\x83\xcb\xf7\xc1\xd4\x2c\x2a\x41\x72\xa6\x90\x41\x6a\x34\xa1\xa6\x98\x7d\x3c\x8f\x31\x2b\x70\x9c\x96\xd6\x2c\x30\x3e\xdd\xe0\x92\x24\x85\x49\xd3\x40\xf8\x3b\x16\xd2\x91\x5d\x7d\x30\x0b\x21\x35\xb4\x2d\xf7\xd6\x5f\xc4\x02\xa1\x6d\x23\xde\x05\x76\x49\x4a\xea\x1b\xb0\xa8\x62\x26\x53\xa3\x19\xd0\xaa\xc2\x98\xc9\x85\x28\x90\xcb\xd4\xb0\x0d\x51\x47\x82\x64\xca\x73\xb1\xf4\x71\xa1\x77\x3d\x40\x70\xb4\x52\xe8\x4a\x44\xda\x4f\x4b\x9d\xe3\x9d\x37\x4c\x9d\x63\xc0\x93\x51\xc4\x3b\xb5\xa2\x99\xc9\x56\x3d\x54\x79\xfa\x14\xfa\xe5\x69\x1f\x9e\xc9\xe5\xa6\x27\xb7\x56\x54\x15\xda\x9e\x53\x27\x87\x98\x6d\xaa\xbc\xb7\xd9\x5d\x43\x67\x2c\x13\x8f\x1d\x71\x2a\x0f\x7b\x2f\x44\x71\xdc\x39\xb5\x28\x08\xb3\xc3\x01\x4d\x23\x73\x08\x7f\x10\xee\x8f\x5a\x69\xd7\xb6\x3e\xc1\x2f\xd1\x8a\x99\x54\x92\x24\xba\x75\x62\xd3\xa0\xce\xda\x76\x97\x2c\xdf\x67\xdb\x34\x60\x85\x2e\x10\x9e\xdf\xe0\x6a\x0c\xcf\x97\x42\xd5\x08\x93\xd8\x2b\x56\x19\x27\xc9\x58\x89\x0e\xf6\x71\x0e\x17\x9d\xc1\x52\x28\x59\xe8\x98\x29\xcc\x89\x81\x36\x5e\xc3\x87\x91\xdb\x32\x9e\x0f\xeb\x10\x9b\x06\x5b\xac\x8c\x6f\x4f\x47\xa6\x6b\xd2\x3f\x50\x5b\xf5\xa9\x46\xbb\xf2\xbd\x23\x51\x0c\x02\x2e\x44\xe1\x8d\x4b\x8f\xc3\x0e\xd6\x3d\xa8\x76\x08\x7a\x34\x6a\x9f\x1a\x17\x47\x61\x23\x4e\xd9\x61\x2d\x7a\x29\xac\x2c\xca\x2f\x5b\x8b\x2e\xe7\x4b\x91\x62\xc3\xaa\xdf\x04\xe1\xf7\xc6\x2e\x04\x01\x3b\x79\x0d\x3f\x0a\x3d\x86\xd7\x27\x27\xdf\xc0\xe9\x9b\xc9\xc9\xd7\x93\x93\x37\x70\x79\x31\x65\x87\xc8\x1f\xe6\xf2\xa0\x9c\xcf\x64\xfb\x3f\xf5\x09\x64\x16\xb3\x07\xdf\xe6\x64\x3f\x9a\x1d\x26\xb1\x7b\x6a\x39\x59\x68\xa1\x58\x12\xf1\x4c\x2e\x8f\xb0\xe6\xe2\x40\x9b\x8e\x08\xf6\xc4\x43\x04\x75\x36\x6c\x43\xc4\x07\x47\x65\x4f\xe5\xc1\x01\x9b\x1b\x43\xbb\xe7\x6b\x95\xfc\x2c\x32\x84\x5b\x49\x25\x44\xa9\xc9\x30\x89\xbe\x8a\xf8\x7a\x01\xb3\xd5\xbd\xdc\x7e\xa8\xb9\x09\xe7\x85\xa4\xb2\x9e\x85\xa9\x59\xf0\x39\x3a\x97\x5b\xf1\x37\x4b\xbe\xdb\x2c\x7d\x9d\x11\xaf\x76\xf0\xa7\x25\xa6\x37\xa6\x26\xa0\x12\xc1\x99\xda\xa6\x08\x1e\x1f\x04\x4d\x1e\xc5\x2f\x50\xd7\x52\x23\x19\xa3\x1c\xb7\x58\xb0\xe4\x11\xe7\xa1\x37\xff\x24\x1c\xc1\x65\x95\xf9\x8f\x7a\xe2\x25\x0b\xbd\xa5\x37\xac\x87\x50\x35\x94\xcb\xdf\x04\x78\xd8\x49\xe4\x2f\x02\x6b\x8f\x4b\xad\xac\x08\x9c\x4d\xef\xe7\xe0\xdc\xf1\xce\xec\x42\x7f\x85\x88\xfa\x7f\x5d\xc6\xfe\xb0\xd8\x81\xe9\xe6\x32\xe1\x1d\xf1\xb9\x58\x8a\xce\xba\x6d\xc7\x52\x58\x10\x73\x71\x37\x15\x4a\x39\x88\xe1\x6a\x34\x68\xf7\x7f\x9e\x19\x2f\x3e\x6b\x87\x84\x73\x67\xf4\x8b\xf1\xe8\xd8\x07\x77\x7d\xd6\x2f\x6e\xa5\xce\xcc\x6d\x68\xb4\x32\x22\x83\x18\xf2\x5a\xa7\x24\x8d\x7e\xf9\x0a\x9a\x6d\xf6\x7b\x6b\xc5\x2a\xac\xac\x21\xe3\x25\x08\x73\x63\xcf\x45\x5a\x86\xa9\x50\xea\xe5\xb6\xe4\xf1\x7d\x72\x6d\xd5\x18\xa4\xce\xf0\xee\x55\x33\xa8\xc9\xbf\x63\x38\x79\x57\x53\x53\x6b\xf2\xd1\xaf\xce\xb6\x61\xed\x76\xdd\x9e\xf5\xed\xdd\x6d\x50\xb7\xc1\x22\xde\xdd\x59\x22\xde\xdd\x84\x37\xf6\x7f\x03\x00\x00\xff\xff\x88\x1c\x69\x6a\x34\x0b\x00\x00"),
},
"/vulns.html": &vfsgen۰CompressedFileInfo{
name: "vulns.html",
modTime: time.Date(2018, 9, 10, 21, 40, 58, 715425786, time.UTC),
uncompressedSize: 2715,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xa4\x56\xdf\x6f\xdb\x36\x10\x7e\xcf\x5f\x71\x21\xfa\x18\x99\x48\x82\xa1\x59\x40\x0b\x5b\x93\x60\x2b\xd0\xfd\x40\x9b\x0e\x1b\x8a\x62\xa0\xa4\xb3\xc4\x86\x22\x35\xf2\xe4\xcd\x13\xf4\xbf\x0f\x12\x2d\x5b\x96\xec\x2c\xc3\xfc\x20\x4b\xbe\xbb\xef\xee\xfb\xee\x74\x74\xd3\x64\xb8\x52\x06\x81\xad\x6b\x6d\x3c\x6b\xdb\x33\x71\x7e\xff\xd3\xdd\xe3\x6f\x3f\x3f\x40\x41\xa5\x8e\xcf\xc4\x79\x14\x7d\x52\x2b\xd0\x04\x6f\x1f\xe0\xf5\xe7\x18\xfa\x8f\xe8\xac\x90\x6a\xe9\xfd\x92\x19\x1b\x7d\xf1\xa0\x29\x52\xf8\x75\xf8\xba\x09\x5f\xaf\x59\x0c\xe2\xfc\x13\x9a\x4c\xad\x3e\x47\xd1\x1e\x6d\x0c\xf5\x02\xb4\x67\x60\x6e\x5e\x02\x73\x2a\x3e\xa7\x2d\x44\xf7\x43\x7c\x24\xbe\x0f\x8c\xa2\xc3\xe0\x02\x65\x16\x9f\xf5\x09\x4b\x24\x09\x69\x21\x9d\x47\x5a\xb2\x9a\x56\xd1\x0d\xdb\x9a\x12\xe9\x11\x0a\x87\xab\x25\xe3\x0c\xc6\xfe\x05\x51\x15\xe1\x1f\xb5\x5a\x2f\xd9\xaf\xd1\xc7\x6f\xa3\x3b\x5b\x56\x92\x54\xa2\x91\x41\x6a\x0d\xa1\xa1\x25\x7b\xfb\xb0\xc4\x2c\xc7\x8b\xb4\x70\xb6\xc4\xe5\xe5\x80\x4b\x8a\x34\xc6\x4d\x03\x8b\xf7\x98\x2b\x4f\x6e\xf3\xf1\xfd\x3b\x68\x5b\x1e\x7e\xaa\x2c\xb4\xed\x6d\x77\xff\x28\x73\x68\x5b\xf8\xa5\xd6\x06\x9d\x4c\x94\x56\xb4\x81\xce\xc1\x91\xe0\x01\x25\x20\x6a\x65\x9e\xc0\xa1\x5e\x32\x95\x5a\xc3\x80\x36\x15\x2e\x99\x2a\x65\x8e\x5c\xa5\x96\x0d\x2c\x3c\x49\x52\x29\x5f\xc9\x75\xe7\xb7\xe8\x4c\x33\x04\x4f\x1b\x8d\xbe\x40\xa4\x69\x58\xea\x3d\x4f\xac\x25\x4f\x4e\x56\x8b\x52\x99\x45\xea\x3d\x03\x1e\x9f\x09\x1e\x14\x15\x89\xcd\x36\x5b\xc4\x4c\xad\x87\x46\x74\x8a\x48\x65\xd0\x6d\xb3\xf5\x76\xbb\xeb\x53\xe2\x50\x66\xa9\xab\xcb\x64\x64\xdf\x56\x15\x0b\xb9\x6b\xc1\x11\xc9\x04\x97\xb1\xe0\x5a\xcd\xe2\x06\x6c\x99\x92\x5a\xe3\x10\x3a\x95\xf6\x30\x54\x70\xab\xe3\xb3\xfd\xe3\x88\x41\x25\x73\x8c\x3a\x8e\x07\x1c\xc2\xc4\x5e\xfe\x87\x56\x0a\x5f\x4a\xad\xe3\xe3\x1d\x0d\x36\xc1\x8b\xcb\x71\x4d\x99\x5a\x8f\x1e\xab\xa1\x22\xc2\xbf\x28\x72\x2a\x2f\x88\xc5\xdf\x61\x87\x46\x98\x81\x35\xb7\xd0\x34\x8b\x7b\x49\xd8\xb1\xab\x46\x74\x9a\x26\xbc\x2e\x8b\x37\x32\xeb\xf2\x7b\xf8\xaa\x6d\x8f\x92\x95\x1a\x1d\x41\x7f\x8d\x32\x69\x72\x74\x0c\x9c\xd5\xb8\xb5\x4c\x04\x68\x9a\x1d\x62\xdb\xc2\xf7\x2a\x2f\x2e\xe0\xce\x29\x52\xa9\xd4\x17\x20\x4d\xc6\xad\x83\x7b\x5c\xa5\xd6\x5c\xc2\x7a\x44\x5c\xa1\x87\x95\xad\x4d\x76\x8a\x6c\xd3\xa0\xc9\xda\x76\xd4\x91\xe2\x2a\xfe\x50\x97\xa5\x74\x1b\xc1\x8b\xab\x91\x2c\xf5\x6e\x98\xb4\xf2\x14\xe5\xce\xd6\xd5\x7c\x98\xe6\x3e\x91\x22\x2c\x27\x8e\xbd\xb3\xaf\xa4\xd9\xcd\xa7\xcc\xf2\x30\x42\x1a\x0d\x2c\x82\x78\x9d\xbc\x9d\xd3\x91\xd8\x24\x7e\xb4\x24\xb5\xe0\xc9\xa4\x82\xd9\xa0\x36\x8d\xeb\xf4\x85\x57\x4f\xb8\xb9\x80\x57\x6b\xa9\x6b\x84\xdb\xe5\x36\xc7\x9b\xcd\x07\x5c\xa3\x53\xb4\x19\x35\xea\xff\x51\xd1\x32\x41\x0d\xfd\x35\x6a\x9a\xd4\x6a\xeb\xfa\xe4\x6d\x0b\x55\xad\xf5\x30\x50\x5b\xaa\xdb\x82\x4e\x53\x6d\x9a\x3e\x1a\xa6\xf5\x1d\x21\x1a\x5a\xb9\xf7\xa8\x0f\x5e\xb5\xe2\x2a\xbe\x47\x92\x4a\xfb\xc3\xc6\xee\x04\xea\x4f\xb7\x7f\x91\x66\xef\x3c\xe8\x18\xc2\x4e\x8c\x79\x25\x0d\x6a\xe8\xaf\x51\x86\x2b\x59\xeb\xe9\x68\xcf\xbc\xfb\x15\xa0\x4c\x7e\x4c\xe6\xe2\xfa\xd0\xb5\xdf\xcf\x9d\x94\xa1\x9c\xc5\x8f\xb2\xc4\x89\x4e\x2f\x6f\x51\x80\xd8\x73\x9e\x74\x6b\x66\x3f\x39\x9b\xbc\xb8\x9e\x0e\xe5\xc1\x3b\x77\x9c\x76\xb7\xd6\x8f\x70\xde\x25\xbe\x47\x9f\x3a\x55\x91\xb2\x66\x36\x0a\x2f\x80\x5f\x59\x4b\xb3\xc5\xda\xbb\x0e\xcb\x7f\x97\xe9\x9d\x32\x4f\x6d\xcb\x80\xa4\xcb\xbb\x03\xfb\xf7\x44\x4b\xf3\x34\xd2\x20\x38\x74\xe7\xc2\x73\x75\x9c\xd8\x34\xd3\xe7\xbd\x7f\x28\xf1\x60\xf9\xa6\x68\xe6\x55\x8b\x2a\xfe\x41\x66\x08\x7f\x2a\x2a\x40\xa4\x36\xc3\x58\x5c\x0b\xde\xdf\x40\xb2\xd9\x33\xea\xfe\x42\xf8\x5b\xce\x73\x45\x45\x9d\x2c\x52\x5b\xf2\x2f\xe8\xfd\xca\xc9\xbf\x59\xfc\xcd\x70\x1b\xce\xb7\x6a\x96\xe3\xae\xc0\xf4\xc9\xd6\x04\x54\x20\x78\x5b\xbb\x14\xa1\xcb\x01\x92\x6e\x9f\xcd\x91\xa3\xa9\x95\x41\xb2\x56\x7b\xee\x30\x67\xf1\x33\xc6\x59\x76\xc1\x83\x0e\xdb\x23\x3e\x88\x28\x78\x38\xf5\x05\x0f\xff\x37\x07\xf1\xfe\x09\x00\x00\xff\xff\x30\xe0\x25\xc3\x9b\x0a\x00\x00"),
},
}
fs["/"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
fs["/repositories.html"].(os.FileInfo),
fs["/tags.html"].(os.FileInfo),
fs["/vulns.html"].(os.FileInfo),
}
return fs
}()
type vfsgen۰FS map[string]interface{}
func (fs vfsgen۰FS) Open(path string) (http.File, error) {
path = pathpkg.Clean("/" + path)
f, ok := fs[path]
if !ok {
return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
}
switch f := f.(type) {
case *vfsgen۰CompressedFileInfo:
gr, err := gzip.NewReader(bytes.NewReader(f.compressedContent))
if err != nil {
// This should never happen because we generate the gzip bytes such that they are always valid.
panic("unexpected error reading own gzip compressed bytes: " + err.Error())
}
return &vfsgen۰CompressedFile{
vfsgen۰CompressedFileInfo: f,
gr: gr,
}, nil
case *vfsgen۰DirInfo:
return &vfsgen۰Dir{
vfsgen۰DirInfo: f,
}, nil
default:
// This should never happen because we generate only the above types.
panic(fmt.Sprintf("unexpected type %T", f))
}
}
// vfsgen۰CompressedFileInfo is a static definition of a gzip compressed file.
type vfsgen۰CompressedFileInfo struct {
name string
modTime time.Time
compressedContent []byte
uncompressedSize int64
}
func (f *vfsgen۰CompressedFileInfo) Readdir(count int) ([]os.FileInfo, error) {
return nil, fmt.Errorf("cannot Readdir from file %s", f.name)
}
func (f *vfsgen۰CompressedFileInfo) Stat() (os.FileInfo, error) { return f, nil }
func (f *vfsgen۰CompressedFileInfo) GzipBytes() []byte {
return f.compressedContent
}
func (f *vfsgen۰CompressedFileInfo) Name() string { return f.name }
func (f *vfsgen۰CompressedFileInfo) Size() int64 { return f.uncompressedSize }
func (f *vfsgen۰CompressedFileInfo) Mode() os.FileMode { return 0444 }
func (f *vfsgen۰CompressedFileInfo) ModTime() time.Time { return f.modTime }
func (f *vfsgen۰CompressedFileInfo) IsDir() bool { return false }
func (f *vfsgen۰CompressedFileInfo) Sys() interface{} { return nil }
// vfsgen۰CompressedFile is an opened compressedFile instance.
type vfsgen۰CompressedFile struct {
*vfsgen۰CompressedFileInfo
gr *gzip.Reader
grPos int64 // Actual gr uncompressed position.
seekPos int64 // Seek uncompressed position.
}
func (f *vfsgen۰CompressedFile) Read(p []byte) (n int, err error) {
if f.grPos > f.seekPos {
// Rewind to beginning.
err = f.gr.Reset(bytes.NewReader(f.compressedContent))
if err != nil {
return 0, err
}
f.grPos = 0
}
if f.grPos < f.seekPos {
// Fast-forward.
_, err = io.CopyN(ioutil.Discard, f.gr, f.seekPos-f.grPos)
if err != nil {
return 0, err
}
f.grPos = f.seekPos
}
n, err = f.gr.Read(p)
f.grPos += int64(n)
f.seekPos = f.grPos
return n, err
}
func (f *vfsgen۰CompressedFile) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekStart:
f.seekPos = 0 + offset
case io.SeekCurrent:
f.seekPos += offset
case io.SeekEnd:
f.seekPos = f.uncompressedSize + offset
default:
panic(fmt.Errorf("invalid whence value: %v", whence))
}
return f.seekPos, nil
}
func (f *vfsgen۰CompressedFile) Close() error {
return f.gr.Close()
}
// vfsgen۰DirInfo is a static definition of a directory.
type vfsgen۰DirInfo struct {
name string
modTime time.Time
entries []os.FileInfo
}
func (d *vfsgen۰DirInfo) Read([]byte) (int, error) {
return 0, fmt.Errorf("cannot Read from directory %s", d.name)
}
func (d *vfsgen۰DirInfo) Close() error { return nil }
func (d *vfsgen۰DirInfo) Stat() (os.FileInfo, error) { return d, nil }
func (d *vfsgen۰DirInfo) Name() string { return d.name }
func (d *vfsgen۰DirInfo) Size() int64 { return 0 }
func (d *vfsgen۰DirInfo) Mode() os.FileMode { return 0755 | os.ModeDir }
func (d *vfsgen۰DirInfo) ModTime() time.Time { return d.modTime }
func (d *vfsgen۰DirInfo) IsDir() bool { return true }
func (d *vfsgen۰DirInfo) Sys() interface{} { return nil }
// vfsgen۰Dir is an opened dir instance.
type vfsgen۰Dir struct {
*vfsgen۰DirInfo
pos int // Position within entries for Seek and Readdir.
}
func (d *vfsgen۰Dir) Seek(offset int64, whence int) (int64, error) {
if offset == 0 && whence == io.SeekStart {
d.pos = 0
return 0, nil
}
return 0, fmt.Errorf("unsupported Seek in directory %s", d.name)
}
func (d *vfsgen۰Dir) Readdir(count int) ([]os.FileInfo, error) {
if d.pos >= len(d.entries) && count > 0 {
return nil, io.EOF
}
if count <= 0 || count > len(d.entries)-d.pos {
count = len(d.entries) - d.pos
}
e := d.entries[d.pos : d.pos+count]
d.pos += count
return e, nil
}

View file

@ -1,52 +1,71 @@
package main
import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"
"github.com/genuinetools/reg/repoutils"
digest "github.com/opencontainers/go-digest"
"github.com/urfave/cli"
"github.com/genuinetools/reg/registry"
)
var layerCommand = cli.Command{
Name: "layer",
Aliases: []string{"download"},
Usage: "download a layer for the specific reference of a repository",
Flags: []cli.Flag{
cli.StringFlag{
Name: "output, o",
Usage: "output file, default to stdout",
},
},
Action: func(c *cli.Context) error {
if len(c.Args()) < 1 {
return fmt.Errorf("pass the name of the repository")
}
const layerHelp = `Download a layer for a repository.`
repo, ref, err := repoutils.GetRepoAndRef(c.Args()[0])
if err != nil {
return err
}
func (cmd *layerCommand) Name() string { return "layer" }
func (cmd *layerCommand) Args() string { return "[OPTIONS] NAME[:TAG|@DIGEST]" }
func (cmd *layerCommand) ShortHelp() string { return layerHelp }
func (cmd *layerCommand) LongHelp() string { return layerHelp }
func (cmd *layerCommand) Hidden() bool { return false }
layer, err := r.DownloadLayer(repo, digest.FromString(ref))
if err != nil {
return err
}
defer layer.Close()
b, err := ioutil.ReadAll(layer)
if err != nil {
return err
}
if c.String("output") != "" {
return ioutil.WriteFile(c.String("output"), b, 0644)
}
fmt.Fprint(os.Stdout, string(b))
return nil
},
func (cmd *layerCommand) Register(fs *flag.FlagSet) {
fs.StringVar(&cmd.output, "output", "", "output file, defaults to stdout")
fs.StringVar(&cmd.output, "o", "", "output file, defaults to stdout")
}
type layerCommand struct {
output string
}
func (cmd *layerCommand) Run(ctx context.Context, args []string) error {
if len(args) < 1 {
return fmt.Errorf("pass the name of the repository")
}
image, err := registry.ParseImage(args[0])
if err != nil {
return err
}
// Create the registry client.
r, err := createRegistryClient(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
}
defer layer.Close()
b, err := ioutil.ReadAll(layer)
if err != nil {
return err
}
if len(cmd.output) > 0 {
return ioutil.WriteFile(cmd.output, b, 0644)
}
fmt.Fprint(os.Stdout, string(b))
return nil
}

33
vendor/github.com/genuinetools/reg/layer_test.go generated vendored 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)
}
}

View file

@ -1,62 +1,86 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"sort"
"strings"
"sync"
"text/tabwriter"
"github.com/urfave/cli"
)
var listCommand = cli.Command{
Name: "list",
Aliases: []string{"ls"},
Usage: "list all repositories",
Action: func(c *cli.Context) error {
// Get the repositories via catalog.
repos, err := r.Catalog("")
if err != nil {
return err
}
const listHelp = `List all repositories.`
fmt.Printf("Repositories for %s\n", auth.ServerAddress)
func (cmd *listCommand) Name() string { return "ls" }
func (cmd *listCommand) Args() string { return "[OPTIONS] REGISTRY_DOMAIN" }
func (cmd *listCommand) ShortHelp() string { return listHelp }
func (cmd *listCommand) LongHelp() string { return listHelp }
func (cmd *listCommand) Hidden() bool { return false }
// Setup the tab writer.
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
func (cmd *listCommand) Register(fs *flag.FlagSet) {}
// Print header.
fmt.Fprintln(w, "REPO\tTAGS")
type listCommand struct{}
var (
l sync.Mutex
wg sync.WaitGroup
)
func (cmd *listCommand) Run(ctx context.Context, args []string) error {
if len(args) < 1 {
return fmt.Errorf("pass the domain of the registry")
}
wg.Add(len(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)
}
out := fmt.Sprintf("%s\t%s\n", repo, strings.Join(tags, ", "))
// Create the registry client.
r, err := createRegistryClient(args[0])
if err != nil {
return err
}
// Get the repositories via catalog.
repos, err := r.Catalog("")
if err != nil {
return err
}
sort.Strings(repos)
// Lock around the tabwriter to prevent garbled output.
// See: https://github.com/genuinetools/reg/issues/54
l.Lock()
w.Write([]byte(out))
l.Unlock()
fmt.Printf("Repositories for %s\n", r.Domain)
wg.Done()
}(repo)
}
wg.Wait()
var (
l sync.Mutex
wg sync.WaitGroup
repoTags = map[string][]string{}
)
w.Flush()
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)
return nil
},
// Lock on the write to the map.
l.Lock()
repoTags[repo] = tags
l.Unlock()
wg.Done()
}(repo)
}
wg.Wait()
// Setup the tab writer.
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
// Print header.
fmt.Fprintln(w, "REPO\tTAGS")
// Sort the repos.
for _, repo := range repos {
w.Write([]byte(fmt.Sprintf("%s\t%s\n", repo, strings.Join(repoTags[repo], ", "))))
}
w.Flush()
return nil
}

View file

@ -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)
t.Fatalf("output: %s, error: %v", 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)
}
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)
}
}

View file

@ -1,109 +1,120 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/docker/docker/api/types"
"github.com/genuinetools/pkg/cli"
"github.com/genuinetools/reg/registry"
"github.com/genuinetools/reg/repoutils"
"github.com/genuinetools/reg/version"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
var (
auth types.AuthConfig
r *registry.Registry
insecure bool
forceNonSSL bool
skipPing bool
timeout time.Duration
username string
password string
debug bool
)
//go:generate go run internal/binutils/generate.go
func main() {
app := cli.NewApp()
app.Name = "reg"
app.Version = fmt.Sprintf("version %s, build %s", version.VERSION, version.GITCOMMIT)
app.Author = "The Genuinetools Authors"
app.Email = "no-reply@butts.com"
app.Usage = "Docker registry v2 client."
// Create a new cli program.
p := cli.NewProgram()
p.Name = "reg"
p.Description = "Docker registry v2 client"
// Set the GitCommit and Version.
p.GitCommit = version.GITCOMMIT
p.Version = version.VERSION
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "debug, d",
Usage: "run in debug mode",
},
cli.BoolFlag{
Name: "insecure, k",
Usage: "do not verify tls certificates",
},
cli.BoolFlag{
Name: "force-non-ssl, f",
Usage: "force allow use of non-ssl",
},
cli.StringFlag{
Name: "username, u",
Usage: "username for the registry",
},
cli.StringFlag{
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",
},
// Build the list of available commands.
p.Commands = []cli.Command{
&digestCommand{},
&layerCommand{},
&listCommand{},
&manifestCommand{},
&removeCommand{},
&serverCommand{},
&tagsCommand{},
&vulnsCommand{},
}
app.Commands = []cli.Command{
deleteCommand,
layerCommand,
listCommand,
manifestCommand,
tagsCommand,
vulnsCommand,
}
// Setup the global flags.
p.FlagSet = flag.NewFlagSet("global", flag.ExitOnError)
p.FlagSet.BoolVar(&insecure, "insecure", false, "do not verify tls certificates")
p.FlagSet.BoolVar(&insecure, "k", false, "do not verify tls certificates")
app.Before = func(c *cli.Context) (err error) {
// Preload initializes any global options and configuration
// before the main or sub commands are run.
if c.GlobalBool("debug") {
p.FlagSet.BoolVar(&forceNonSSL, "force-non-ssl", false, "force allow use of non-ssl")
p.FlagSet.BoolVar(&forceNonSSL, "f", false, "force allow use of non-ssl")
p.FlagSet.BoolVar(&skipPing, "skip-ping", false, "skip pinging the registry while establishing connection")
p.FlagSet.DurationVar(&timeout, "timeout", time.Minute, "timeout for HTTP requests")
p.FlagSet.StringVar(&username, "username", "", "username for the registry")
p.FlagSet.StringVar(&username, "u", "", "username for the registry")
p.FlagSet.StringVar(&password, "password", "", "password for the registry")
p.FlagSet.StringVar(&password, "p", "", "password for the registry")
p.FlagSet.BoolVar(&debug, "d", false, "enable debug logging")
// Set the before function.
p.Before = func(ctx context.Context) error {
// On ^C, or SIGTERM handle exit.
signals := make(chan os.Signal, 0)
signal.Notify(signals, os.Interrupt)
signal.Notify(signals, syscall.SIGTERM)
_, cancel := context.WithCancel(ctx)
go func() {
for sig := range signals {
cancel()
logrus.Infof("Received %s, exiting.", sig.String())
os.Exit(0)
}
}()
// Set the log level.
if debug {
logrus.SetLevel(logrus.DebugLevel)
}
if len(c.Args()) == 0 {
return
}
if c.Args()[0] == "help" {
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")
}
// create the registry client
if c.GlobalBool("insecure") {
r, err = registry.NewInsecure(auth, c.GlobalBool("debug"))
if err != nil {
return err
}
return
}
r, err = registry.New(auth, c.GlobalBool("debug"))
return err
return nil
}
if err := app.Run(os.Args); err != nil {
logrus.Fatal(err)
}
// Run our program.
p.Run()
}
func createRegistryClient(domain string) (*registry.Registry, error) {
auth, err := repoutils.GetAuthConfig(username, password, domain)
if err != nil {
return nil, err
}
// Prevent non-ssl unless explicitly forced
if !forceNonSSL && strings.HasPrefix(auth.ServerAddress, "http:") {
return nil, fmt.Errorf("Attempted to use insecure protocol! Use force-non-ssl option to force")
}
// Create the registry client.
return registry.New(auth, registry.Opt{
Insecure: insecure,
Debug: debug,
SkipPing: skipPing,
Timeout: timeout,
})
}

View file

@ -13,6 +13,10 @@ import (
"github.com/genuinetools/reg/testutils"
)
const (
domain = "localhost:5000"
)
var (
exeSuffix string // ".exe" on Windows
@ -32,6 +36,7 @@ var (
password: "testing",
},
}
registryHelper *testutils.RegistryHelper
)
func init() {
@ -62,14 +67,26 @@ func TestMain(m *testing.M) {
panic(fmt.Errorf("could not connect to docker: %v", err))
}
// start the clair containers.
dbID, clairID, err := testutils.StartClair(dcli)
if err != nil {
testutils.RemoveContainer(dcli, dbID, clairID)
panic(fmt.Errorf("starting clair containers failed: %v", err))
}
for _, regConfig := range registryConfigs {
// start each registry
regID, _, err := testutils.StartRegistry(dcli, regConfig.config, regConfig.username, regConfig.password)
if err != nil {
testutils.RemoveContainer(dcli, regID)
testutils.RemoveContainer(dcli, dbID, clairID, regID)
panic(fmt.Errorf("starting registry container %s failed: %v", regConfig.config, err))
}
registryHelper, err = testutils.NewRegistryHelper(dcli, regConfig.username, regConfig.password, domain)
if err != nil {
panic(fmt.Errorf("creating registry helper %s failed: %v", regConfig.config, err))
}
flag.Parse()
merr := m.Run()
@ -79,18 +96,27 @@ func TestMain(m *testing.M) {
}
if merr != 0 {
testutils.RemoveContainer(dcli, dbID, clairID)
fmt.Printf("testing config %s failed\n", regConfig.config)
os.Exit(merr)
}
}
// remove clair containers.
if err := testutils.RemoveContainer(dcli, dbID, clairID); err != nil {
log.Printf("couldn't remove clair containers: %v", err)
}
os.Exit(0)
}
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 := []string{args[0], "-d", "-k"}
if len(args) > 1 {
newargs = append(newargs, args[1:]...)
}
cmd := exec.Command(prog, newargs...)
out, err := cmd.CombinedOutput()
return string(out), err

View file

@ -1,54 +1,66 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"github.com/genuinetools/reg/repoutils"
"github.com/urfave/cli"
"github.com/genuinetools/reg/registry"
)
var manifestCommand = cli.Command{
Name: "manifest",
Usage: "get the json manifest for the specific reference of a repository",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "v1",
Usage: "force the version of the manifest retreived to v1",
},
},
Action: func(c *cli.Context) error {
if len(c.Args()) < 1 {
return fmt.Errorf("pass the name of the repository")
}
const manifestHelp = `Get the json manifest for a repository.`
repo, ref, err := repoutils.GetRepoAndRef(c.Args()[0])
if err != nil {
return err
}
func (cmd *manifestCommand) Name() string { return "manifest" }
func (cmd *manifestCommand) Args() string { return "[OPTIONS] NAME[:TAG|@DIGEST]" }
func (cmd *manifestCommand) ShortHelp() string { return manifestHelp }
func (cmd *manifestCommand) LongHelp() string { return manifestHelp }
func (cmd *manifestCommand) Hidden() bool { return false }
var manifest interface{}
if c.Bool("v1") {
// Get the v1 manifest if it was explicitly asked for.
manifest, err = r.ManifestV1(repo, ref)
if err != nil {
return err
}
} else {
// Get the v2 manifest.
manifest, err = r.Manifest(repo, ref)
if err != nil {
return err
}
}
b, err := json.MarshalIndent(manifest, " ", " ")
if err != nil {
return err
}
fmt.Println(string(b))
return nil
},
func (cmd *manifestCommand) Register(fs *flag.FlagSet) {
fs.BoolVar(&cmd.v1, "v1", false, "force the version of the manifest retrieved to v1")
}
type manifestCommand struct {
v1 bool
}
func (cmd *manifestCommand) Run(ctx context.Context, args []string) error {
if len(args) < 1 {
return fmt.Errorf("pass the name of the repository")
}
image, err := registry.ParseImage(args[0])
if err != nil {
return err
}
// Create the registry client.
r, err := createRegistryClient(image.Domain)
if err != nil {
return err
}
var manifest interface{}
if cmd.v1 {
// Get the v1 manifest if it was explicitly asked for.
manifest, err = r.ManifestV1(image.Path, image.Reference())
if err != nil {
return err
}
} else {
// Get the v2 manifest.
manifest, err = r.Manifest(image.Path, image.Reference())
if err != nil {
return err
}
}
b, err := json.MarshalIndent(manifest, " ", " ")
if err != nil {
return err
}
fmt.Println(string(b))
return nil
}

31
vendor/github.com/genuinetools/reg/manifest_test.go generated vendored 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

@ -1,6 +1,7 @@
package registry
import (
"errors"
"fmt"
"net/http"
"net/url"
@ -12,6 +13,9 @@ var (
bearerRegex = regexp.MustCompile(
`^\s*Bearer\s+(.*)$`)
basicRegex = regexp.MustCompile(`^\s*Basic\s+.*$`)
// ErrBasicAuth indicates that the repository requires basic rather than token authentication.
ErrBasicAuth = errors.New("basic auth required")
)
func parseAuthHeader(header http.Header) (*authService, error) {
@ -25,14 +29,14 @@ func parseAuthHeader(header http.Header) (*authService, error) {
func parseChallenge(challengeHeader string) (*authService, error) {
if basicRegex.MatchString(challengeHeader) {
return nil, nil
return nil, ErrBasicAuth
}
match := bearerRegex.FindAllStringSubmatch(challengeHeader, -1)
if d := len(match); d != 1 {
return nil, fmt.Errorf("malformed auth challenge header: '%s', %d", challengeHeader, d)
}
parts := strings.Split(strings.TrimSpace(match[0][1]), ",")
parts := strings.SplitN(strings.TrimSpace(match[0][1]), ",", 3)
var realm, service string
var scope []string

View file

@ -1,7 +1,6 @@
package registry
import (
"reflect"
"strings"
"testing"
)
@ -22,13 +21,13 @@ func (asm authServiceMock) equalTo(v *authService) bool {
if asm.service != v.Service {
return false
}
if reflect.DeepEqual(asm.scope, v.Scope) {
return false
for i, v := range v.Scope {
if v != asm.scope[i] {
return false
}
}
if asm.realm != v.Realm.String() {
return false
}
return true
return asm.realm == v.Realm.String()
}
func TestParseChallenge(t *testing.T) {
@ -40,8 +39,47 @@ func TestParseChallenge(t *testing.T) {
realm: "https://foobar.com/api/v1/token",
},
},
{
header: `Bearer realm="https://r.j3ss.co/auth",service="Docker registry",scope="repository:chrome:pull"`,
value: authServiceMock{
service: "Docker registry",
realm: "https://r.j3ss.co/auth",
scope: []string{"repository:chrome:pull"},
},
},
{
header: `Basic realm="https://r.j3ss.co/auth",service="Docker registry"`,
errorString: "basic auth required",
},
{
header: `Basic realm="Registry Realm",service="Docker registry"`,
errorString: "basic auth required",
},
}
for _, tc := range challengeHeaderCases {
val, err := parseChallenge(tc.header)
if err != nil && !strings.Contains(err.Error(), tc.errorString) {
t.Fatalf("expected error to contain %v, got %s", tc.errorString, err)
}
if err == nil && !tc.value.equalTo(val) {
t.Fatalf("got %v, expected %v", val, tc.value)
}
}
}
func TestParseChallengePush(t *testing.T) {
challengeHeaderCases := []challengeTestCase{
{
header: `Bearer realm="https://foo.com/v2/token",service="foo.com",scope="repository:pdr/tls:pull,push"`,
value: authServiceMock{
realm: "https://foo.com/v2/token",
service: "foo.com",
scope: []string{"repository:pdr/tls:pull,push"},
},
},
}
for _, tc := range challengeHeaderCases {
val, err := parseChallenge(tc.header)
if err != nil && !strings.Contains(err.Error(), tc.errorString) {

View file

@ -15,7 +15,7 @@ type BasicTransport struct {
// RoundTrip defines the round tripper for basic auth transport.
func (t *BasicTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if strings.HasPrefix(req.URL.String(), t.URL) {
if strings.HasPrefix(req.URL.String(), t.URL) && req.Header.Get("Authorization") == "" {
if t.Username != "" || t.Password != "" {
req.SetBasicAuth(t.Username, t.Password)
}

View file

@ -0,0 +1,24 @@
package registry
import (
"net/http"
)
// CustomTransport defines the data structure for custom http.Request options.
type CustomTransport struct {
Transport http.RoundTripper
Headers map[string]string
}
// RoundTrip defines the round tripper for the error transport.
func (t *CustomTransport) RoundTrip(request *http.Request) (*http.Response, error) {
if len(t.Headers) != 0 {
for header, value := range t.Headers {
request.Header.Add(header, value)
}
}
resp, err := t.Transport.RoundTrip(request)
return resp, err
}

View file

@ -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)
@ -32,7 +20,7 @@ func (r *Registry) Delete(repository, digest string) error {
return err
}
req.Header.Set("Accept", schema2.MediaTypeManifest)
req.Header.Add("Accept", fmt.Sprintf("%s;q=0.9", schema2.MediaTypeManifest))
resp, err := r.Client.Do(req)
if err != nil {
return err

View file

@ -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.Add("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.Parse(resp.Header.Get("Docker-Content-Digest"))
}

View file

@ -0,0 +1,49 @@
package registry
import (
"testing"
"github.com/genuinetools/reg/repoutils"
)
func TestDigestFromDockerHub(t *testing.T) {
auth, err := repoutils.GetAuthConfig("", "", "docker.io")
if err != nil {
t.Fatalf("Could not get auth config: %s", err)
}
r, err := New(auth, Opt{})
if err != nil {
t.Fatalf("Could not create registry instance: %s", err)
}
d, err := r.Digest(Image{Domain: "docker.io", Path: "library/alpine", Tag: "latest"})
if err != nil {
t.Fatalf("Could not get digest: %s", err)
}
if d == "" {
t.Error("Empty digest received")
}
}
func TestDigestFromGCR(t *testing.T) {
auth, err := repoutils.GetAuthConfig("", "", "gcr.io")
if err != nil {
t.Fatalf("Could not get auth config: %s", err)
}
r, err := New(auth, Opt{})
if err != nil {
t.Fatalf("Could not create registry instance: %s", err)
}
d, err := r.Digest(Image{Domain: "gcr.io", Path: "google_containers/hyperkube", Tag: "v1.9.9"})
if err != nil {
t.Fatalf("Could not get digest: %s", err)
}
if d == "" {
t.Error("Empty digest received")
}
}

67
vendor/github.com/genuinetools/reg/registry/image.go generated vendored 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

@ -5,6 +5,7 @@ import (
"net/http"
"net/url"
"fmt"
"github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest"
)
@ -24,7 +25,7 @@ func (r *Registry) DownloadLayer(repository string, digest digest.Digest) (io.Re
// UploadLayer uploads a specific layer by digest for a repository.
func (r *Registry) UploadLayer(repository string, digest reference.Reference, content io.Reader) error {
uploadURL, err := r.initiateUpload(repository)
uploadURL, token, err := r.initiateUpload(repository)
if err != nil {
return err
}
@ -39,6 +40,7 @@ func (r *Registry) UploadLayer(repository string, digest reference.Reference, co
return err
}
upload.Header.Set("Content-Type", "application/octet-stream")
upload.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
_, err = r.Client.Do(upload)
return err
@ -70,20 +72,21 @@ func (r *Registry) HasLayer(repository string, digest digest.Digest) (bool, erro
return false, err
}
func (r *Registry) initiateUpload(repository string) (*url.URL, error) {
func (r *Registry) initiateUpload(repository string) (*url.URL, string, error) {
initiateURL := r.url("/v2/%s/blobs/uploads/", repository)
r.Logf("registry.layer.initiate-upload url=%s repository=%s", initiateURL, repository)
resp, err := r.Client.Post(initiateURL, "application/octet-stream", nil)
if err != nil {
return nil, err
return nil, "", err
}
token := resp.Header.Get("Request-Token")
defer resp.Body.Close()
location := resp.Header.Get("Location")
locationURL, err := url.Parse(location)
if err != nil {
return nil, err
return nil, token, err
}
return locationURL, nil
return locationURL, token, nil
}

View file

@ -1,6 +1,9 @@
package registry
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
@ -20,8 +23,7 @@ func (r *Registry) Manifest(repository, ref string) (distribution.Manifest, erro
return nil, err
}
req.Header.Add("Accept", schema2.MediaTypeManifest)
req.Header.Add("Accept", manifestlist.MediaTypeManifestList)
req.Header.Add("Accept", fmt.Sprintf("%s;q=0.9", schema2.MediaTypeManifest))
resp, err := r.Client.Do(req)
if err != nil {
@ -84,3 +86,26 @@ 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)
b, err := json.Marshal(manifest)
if err != nil {
return err
}
req, err := http.NewRequest("PUT", url, bytes.NewBuffer(b))
if err != nil {
return err
}
req.Header.Set("Content-Type", schema2.MediaTypeManifest)
resp, err := r.Client.Do(req)
if resp != nil {
defer resp.Body.Close()
}
return err
}

View file

@ -8,6 +8,7 @@ import (
"net/http"
"regexp"
"strings"
"time"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema2"
@ -22,6 +23,7 @@ type Registry struct {
Password string
Client *http.Client
Logf LogfCallback
Opt Opt
}
var reProtocol = regexp.MustCompile("^https?://")
@ -37,26 +39,31 @@ func Log(format string, args ...interface{}) {
log.Printf(format, args...)
}
// Opt holds the options for a new registry.
type Opt struct {
Insecure bool
Debug bool
SkipPing bool
Timeout time.Duration
Headers map[string]string
}
// New creates a new Registry struct with the given URL and credentials.
func New(auth types.AuthConfig, debug bool) (*Registry, error) {
func New(auth types.AuthConfig, opt Opt) (*Registry, error) {
transport := http.DefaultTransport
return newFromTransport(auth, transport, debug)
}
// NewInsecure creates a new Registry struct with the given URL and credentials,
// using a http.Transport that will not verify an SSL certificate.
func NewInsecure(auth types.AuthConfig, debug bool) (*Registry, error) {
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
if opt.Insecure {
transport = &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
}
return newFromTransport(auth, transport, debug)
return newFromTransport(auth, transport, opt)
}
func newFromTransport(auth types.AuthConfig, transport http.RoundTripper, debug bool) (*Registry, error) {
func newFromTransport(auth types.AuthConfig, transport http.RoundTripper, opt Opt) (*Registry, error) {
url := strings.TrimSuffix(auth.ServerAddress, "/")
if !reProtocol.MatchString(url) {
@ -77,10 +84,14 @@ func newFromTransport(auth types.AuthConfig, transport http.RoundTripper, debug
errorTransport := &ErrorTransport{
Transport: basicAuthTransport,
}
customTransport := &CustomTransport{
Transport: errorTransport,
Headers: opt.Headers,
}
// set the logging
logf := Quiet
if debug {
if opt.Debug {
logf = Log
}
@ -88,15 +99,19 @@ func newFromTransport(auth types.AuthConfig, transport http.RoundTripper, debug
URL: url,
Domain: reProtocol.ReplaceAllString(url, ""),
Client: &http.Client{
Transport: errorTransport,
Timeout: opt.Timeout,
Transport: customTransport,
},
Username: auth.Username,
Password: auth.Password,
Logf: logf,
Opt: opt,
}
if err := registry.Ping(); err != nil {
return nil, err
if !opt.SkipPing {
if err := registry.Ping(); err != nil {
return nil, err
}
}
return registry, nil
@ -115,8 +130,7 @@ func (r *Registry) getJSON(url string, response interface{}, addV2Header bool) (
return nil, err
}
if addV2Header {
req.Header.Add("Accept", schema2.MediaTypeManifest)
req.Header.Add("Accept", manifestlist.MediaTypeManifestList)
req.Header.Add("Accept", fmt.Sprintf("%s,%s;q=0.9", schema2.MediaTypeManifest, manifestlist.MediaTypeManifestList))
}
resp, err := r.Client.Do(req)
if err != nil {

View file

@ -1,6 +1,8 @@
package registry
import (
"crypto/tls"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
@ -44,7 +46,11 @@ func (t *TokenTransport) authAndRetry(authService *authService, req *http.Reques
return authResp, err
}
return t.retry(req, token)
response, err := t.retry(req, token)
if response != nil {
response.Header.Set("request-token", token)
}
return response, err
}
func (t *TokenTransport) auth(authService *authService) (string, *http.Response, error) {
@ -114,7 +120,8 @@ func isTokenDemand(resp *http.Response) (*authService, error) {
return parseAuthHeader(resp.Header)
}
// Token returns the required token for the specific resource url.
// Token returns the required token for the specific resource url. If the registry requires basic authentication, this
// function returns ErrBasicAuth.
func (r *Registry) Token(url string) (string, error) {
r.Logf("registry.token url=%s", url)
@ -123,7 +130,19 @@ func (r *Registry) Token(url string) (string, error) {
return "", err
}
resp, err := r.Client.Do(req)
client := http.DefaultClient
if r.Opt.Insecure {
client = &http.Client{
Timeout: r.Opt.Timeout,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
@ -143,7 +162,7 @@ func (r *Registry) Token(url string) (string, error) {
if err != nil {
return "", err
}
resp, err = r.Client.Do(authReq)
resp, err = http.DefaultClient.Do(authReq)
if err != nil {
return "", err
}
@ -164,3 +183,26 @@ func (r *Registry) Token(url string) (string, error) {
return authToken.Token, nil
}
// Headers returns the authorization headers for a specific uri.
func (r *Registry) Headers(uri string) (map[string]string, error) {
// Get the token.
token, err := r.Token(uri)
if err != nil {
if err == ErrBasicAuth {
// If we couldn't get a token because the server requires basic auth, just return basic auth headers.
return map[string]string{
"Authorization": fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(r.Username+":"+r.Password))),
}, nil
}
}
if len(token) < 1 {
r.Logf("got empty token for %s", uri)
return map[string]string{}, nil
}
return map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", token),
}, nil
}

View file

@ -0,0 +1,38 @@
package registry
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/docker/docker/api/types"
)
func TestErrBasicAuth(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
w.Header().Set("www-authenticate", `Basic realm="Registry Realm",service="Docker registry"`)
w.WriteHeader(http.StatusUnauthorized)
} else {
w.WriteHeader(http.StatusOK)
}
}))
defer ts.Close()
authConfig := types.AuthConfig{
Username: "j3ss",
Password: "ss3j",
ServerAddress: ts.URL,
}
r, err := New(authConfig, Opt{Insecure: true, Debug: true})
if err != nil {
t.Fatalf("expected no error creating client, got %v", err)
}
token, err := r.Token(ts.URL)
if err != ErrBasicAuth {
t.Fatalf("expected ErrBasicAuth getting token, got %v", err)
}
if token != "" {
t.Fatalf("expected empty token, got %v", err)
}
}

56
vendor/github.com/genuinetools/reg/remove.go generated vendored Normal file
View file

@ -0,0 +1,56 @@
package main
import (
"context"
"flag"
"fmt"
"github.com/genuinetools/reg/registry"
)
const removeHelp = `Delete a specific reference of a repository.`
func (cmd *removeCommand) Name() string { return "rm" }
func (cmd *removeCommand) Args() string { return "[OPTIONS] NAME[:TAG|@DIGEST]" }
func (cmd *removeCommand) ShortHelp() string { return removeHelp }
func (cmd *removeCommand) LongHelp() string { return removeHelp }
func (cmd *removeCommand) Hidden() bool { return false }
func (cmd *removeCommand) Register(fs *flag.FlagSet) {}
type removeCommand struct{}
func (cmd *removeCommand) Run(ctx context.Context, args []string) error {
if len(args) < 1 {
return fmt.Errorf("pass the name of the repository")
}
image, err := registry.ParseImage(args[0])
if err != nil {
return err
}
// Create the registry client.
r, err := createRegistryClient(image.Domain)
if err != nil {
return err
}
// 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
}

46
vendor/github.com/genuinetools/reg/remove_test.go generated vendored Normal file
View file

@ -0,0 +1,46 @@
package main
import (
"fmt"
"strings"
"testing"
)
func teardownTest(t *testing.T) {
if err := registryHelper.RefillRegistry("busybox:glibc"); err != nil {
t.Fatalf("adding image after remove failed: +%v", err)
}
}
func TestRemove(t *testing.T) {
defer teardownTest(t)
// Make sure we have busybox in list.
out, err := run("ls", domain)
if err != nil {
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", fmt.Sprintf("%s/busybox:glibc", domain)); err != nil {
t.Fatalf("output: %s, error: %v", out, err)
}
// Make sure we have removed busybox:glibc.
out, err = run("ls", domain)
if err != nil {
t.Fatalf("output: %s, error: %v", out, err)
}
expected = `REPO TAGS
alpine 3.5, latest
busybox latest, musl`
if !strings.HasSuffix(strings.TrimSpace(out), expected) {
t.Fatalf("expected to contain: %s\ngot: %s", expected, out)
}
}

View file

@ -7,6 +7,7 @@ import (
"github.com/docker/distribution/reference"
"github.com/docker/docker-ce/components/cli/cli/config"
"github.com/docker/docker/api/types"
"github.com/sirupsen/logrus"
)
const (
@ -80,7 +81,7 @@ func GetAuthConfig(username, password, registry string) (types.AuthConfig, error
}
}
fmt.Printf("Using registry %q with no authentication\n", registry)
logrus.Debugf("Using registry %q with no authentication", registry)
// Otherwise just use the registry with no auth.
return setDefaultRegistry(types.AuthConfig{

185
vendor/github.com/genuinetools/reg/server.go generated vendored Normal file
View file

@ -0,0 +1,185 @@
package main
import (
"context"
"flag"
"fmt"
"html/template"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/genuinetools/reg/clair"
"github.com/genuinetools/reg/internal/binutils/static"
"github.com/genuinetools/reg/internal/binutils/templates"
"github.com/gorilla/mux"
wordwrap "github.com/mitchellh/go-wordwrap"
"github.com/shurcooL/httpfs/html/vfstemplate"
"github.com/sirupsen/logrus"
)
const serverHelp = `Run a static UI server for a registry.`
func (cmd *serverCommand) Name() string { return "server" }
func (cmd *serverCommand) Args() string { return "[OPTIONS]" }
func (cmd *serverCommand) ShortHelp() string { return serverHelp }
func (cmd *serverCommand) LongHelp() string { return serverHelp }
func (cmd *serverCommand) Hidden() bool { return false }
func (cmd *serverCommand) Register(fs *flag.FlagSet) {
fs.DurationVar(&cmd.interval, "interval", time.Hour, "interval to generate new index.html's at")
fs.StringVar(&cmd.registryServer, "registry", "", "URL to the private registry (ex. r.j3ss.co)")
fs.StringVar(&cmd.registryServer, "r", "", "URL to the private registry (ex. r.j3ss.co)")
fs.StringVar(&cmd.clairServer, "clair", "", "url to clair instance")
fs.StringVar(&cmd.cert, "cert", "", "path to ssl cert")
fs.StringVar(&cmd.key, "key", "", "path to ssl key")
fs.StringVar(&cmd.listenAddress, "listen-address", "", "address to listen on")
fs.StringVar(&cmd.port, "port", "8080", "port for server to run on")
fs.StringVar(&cmd.assetPath, "asset-path", "", "Path to assets and templates")
fs.BoolVar(&cmd.generateAndExit, "once", false, "generate the templates once and then exit")
}
type serverCommand struct {
interval time.Duration
registryServer string
clairServer string
generateAndExit bool
cert string
key string
listenAddress string
port string
assetPath string
}
func (cmd *serverCommand) Run(ctx context.Context, args []string) error {
// Create the registry client.
r, err := createRegistryClient(cmd.registryServer)
if err != nil {
return err
}
// Create the registry controller for the handlers.
rc := registryController{
reg: r,
generateOnly: cmd.generateAndExit,
}
// Create a clair client if the user passed in a server address.
if len(cmd.clairServer) > 0 {
rc.cl, err = clair.New(cmd.clairServer, clair.Opt{
Insecure: insecure,
Debug: debug,
Timeout: timeout,
})
if err != nil {
return fmt.Errorf("creation of clair client at %s failed: %v", cmd.clairServer, err)
}
} else {
rc.cl = nil
}
// Get the path to the asset directory.
assetDir := cmd.assetPath
if len(cmd.assetPath) <= 0 {
assetDir, err = os.Getwd()
if err != nil {
return err
}
}
staticDir := filepath.Join(assetDir, "static")
funcMap := template.FuncMap{
"trim": func(s string) string {
return wordwrap.WrapString(s, 80)
},
"color": func(s string) string {
switch s = strings.ToLower(s); s {
case "high":
return "danger"
case "critical":
return "danger"
case "defcon1":
return "danger"
case "medium":
return "warning"
case "low":
return "info"
case "negligible":
return "info"
case "unknown":
return "default"
default:
return "default"
}
},
}
rc.tmpl = template.New("").Funcs(funcMap)
rc.tmpl = template.Must(vfstemplate.ParseGlob(templates.Assets, rc.tmpl, "*.html"))
// Create the initial index.
logrus.Info("creating initial static index")
if err := rc.repositories(staticDir); err != nil {
return fmt.Errorf("creating index failed: %v", err)
}
if cmd.generateAndExit {
logrus.Info("output generated, exiting...")
return nil
}
rc.interval = cmd.interval
ticker := time.NewTicker(rc.interval)
go func() {
// Create more indexes every X minutes based off interval.
for range ticker.C {
logrus.Info("creating timer based static index")
if err := rc.repositories(staticDir); err != nil {
logrus.Warnf("creating static index failed: %v", err)
}
}
}()
// Create mux server.
mux := mux.NewRouter()
mux.UseEncodedPath()
// Static files handler.
mux.HandleFunc("/repo/{repo}/tags", rc.tagsHandler)
mux.HandleFunc("/repo/{repo}/tags/", rc.tagsHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}", rc.vulnerabilitiesHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}/", rc.vulnerabilitiesHandler)
// Add the vulns endpoints if we have a client for a clair server.
if rc.cl != nil {
logrus.Infof("adding clair handlers...")
mux.HandleFunc("/repo/{repo}/tag/{tag}/vulns", rc.vulnerabilitiesHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}/vulns/", rc.vulnerabilitiesHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}/vulns.json", rc.vulnerabilitiesHandler)
}
// Serve the static assets.
staticAssetsHandler := http.FileServer(static.Assets)
mux.PathPrefix("/static/").Handler(http.StripPrefix("/static/", staticAssetsHandler))
staticHandler := http.FileServer(http.Dir(staticDir))
mux.Handle("/", staticHandler)
// Set up the server.
server := &http.Server{
Addr: cmd.listenAddress + ":" + cmd.port,
Handler: mux,
}
logrus.Infof("Starting server on port %q", cmd.port)
if len(cmd.cert) > 0 && len(cmd.key) > 0 {
return server.ListenAndServeTLS(cmd.cert, cmd.key)
}
return server.ListenAndServe()
}

BIN
vendor/github.com/genuinetools/reg/server/home.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View file

@ -0,0 +1,596 @@
/*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2017 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*!
* Generated using the Bootstrap Customizer (https://getbootstrap.com/customize/?id=5343e9cbe6bb765ae40092b46ea944d5)
* Config saved to config.json and https://gist.github.com/5343e9cbe6bb765ae40092b46ea944d5
*/
/*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
.btn-default,
.btn-primary,
.btn-success,
.btn-info,
.btn-warning,
.btn-danger {
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
}
.btn-default:active,
.btn-primary:active,
.btn-success:active,
.btn-info:active,
.btn-warning:active,
.btn-danger:active,
.btn-default.active,
.btn-primary.active,
.btn-success.active,
.btn-info.active,
.btn-warning.active,
.btn-danger.active {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.btn-default.disabled,
.btn-primary.disabled,
.btn-success.disabled,
.btn-info.disabled,
.btn-warning.disabled,
.btn-danger.disabled,
.btn-default[disabled],
.btn-primary[disabled],
.btn-success[disabled],
.btn-info[disabled],
.btn-warning[disabled],
.btn-danger[disabled],
fieldset[disabled] .btn-default,
fieldset[disabled] .btn-primary,
fieldset[disabled] .btn-success,
fieldset[disabled] .btn-info,
fieldset[disabled] .btn-warning,
fieldset[disabled] .btn-danger {
-webkit-box-shadow: none;
box-shadow: none;
}
.btn-default .badge,
.btn-primary .badge,
.btn-success .badge,
.btn-info .badge,
.btn-warning .badge,
.btn-danger .badge {
text-shadow: none;
}
.btn:active,
.btn.active {
background-image: none;
}
.btn-default {
background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
background-image: -o-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#e0e0e0));
background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #dbdbdb;
text-shadow: 0 1px 0 #fff;
border-color: #ccc;
}
.btn-default:hover,
.btn-default:focus {
background-color: #e0e0e0;
background-position: 0 -15px;
}
.btn-default:active,
.btn-default.active {
background-color: #e0e0e0;
border-color: #dbdbdb;
}
.btn-default.disabled,
.btn-default[disabled],
fieldset[disabled] .btn-default,
.btn-default.disabled:hover,
.btn-default[disabled]:hover,
fieldset[disabled] .btn-default:hover,
.btn-default.disabled:focus,
.btn-default[disabled]:focus,
fieldset[disabled] .btn-default:focus,
.btn-default.disabled.focus,
.btn-default[disabled].focus,
fieldset[disabled] .btn-default.focus,
.btn-default.disabled:active,
.btn-default[disabled]:active,
fieldset[disabled] .btn-default:active,
.btn-default.disabled.active,
.btn-default[disabled].active,
fieldset[disabled] .btn-default.active {
background-color: #e0e0e0;
background-image: none;
}
.btn-primary {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #245580;
}
.btn-primary:hover,
.btn-primary:focus {
background-color: #265a88;
background-position: 0 -15px;
}
.btn-primary:active,
.btn-primary.active {
background-color: #265a88;
border-color: #245580;
}
.btn-primary.disabled,
.btn-primary[disabled],
fieldset[disabled] .btn-primary,
.btn-primary.disabled:hover,
.btn-primary[disabled]:hover,
fieldset[disabled] .btn-primary:hover,
.btn-primary.disabled:focus,
.btn-primary[disabled]:focus,
fieldset[disabled] .btn-primary:focus,
.btn-primary.disabled.focus,
.btn-primary[disabled].focus,
fieldset[disabled] .btn-primary.focus,
.btn-primary.disabled:active,
.btn-primary[disabled]:active,
fieldset[disabled] .btn-primary:active,
.btn-primary.disabled.active,
.btn-primary[disabled].active,
fieldset[disabled] .btn-primary.active {
background-color: #265a88;
background-image: none;
}
.btn-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #3e8f3e;
}
.btn-success:hover,
.btn-success:focus {
background-color: #419641;
background-position: 0 -15px;
}
.btn-success:active,
.btn-success.active {
background-color: #419641;
border-color: #3e8f3e;
}
.btn-success.disabled,
.btn-success[disabled],
fieldset[disabled] .btn-success,
.btn-success.disabled:hover,
.btn-success[disabled]:hover,
fieldset[disabled] .btn-success:hover,
.btn-success.disabled:focus,
.btn-success[disabled]:focus,
fieldset[disabled] .btn-success:focus,
.btn-success.disabled.focus,
.btn-success[disabled].focus,
fieldset[disabled] .btn-success.focus,
.btn-success.disabled:active,
.btn-success[disabled]:active,
fieldset[disabled] .btn-success:active,
.btn-success.disabled.active,
.btn-success[disabled].active,
fieldset[disabled] .btn-success.active {
background-color: #419641;
background-image: none;
}
.btn-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #28a4c9;
}
.btn-info:hover,
.btn-info:focus {
background-color: #2aabd2;
background-position: 0 -15px;
}
.btn-info:active,
.btn-info.active {
background-color: #2aabd2;
border-color: #28a4c9;
}
.btn-info.disabled,
.btn-info[disabled],
fieldset[disabled] .btn-info,
.btn-info.disabled:hover,
.btn-info[disabled]:hover,
fieldset[disabled] .btn-info:hover,
.btn-info.disabled:focus,
.btn-info[disabled]:focus,
fieldset[disabled] .btn-info:focus,
.btn-info.disabled.focus,
.btn-info[disabled].focus,
fieldset[disabled] .btn-info.focus,
.btn-info.disabled:active,
.btn-info[disabled]:active,
fieldset[disabled] .btn-info:active,
.btn-info.disabled.active,
.btn-info[disabled].active,
fieldset[disabled] .btn-info.active {
background-color: #2aabd2;
background-image: none;
}
.btn-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #e38d13;
}
.btn-warning:hover,
.btn-warning:focus {
background-color: #eb9316;
background-position: 0 -15px;
}
.btn-warning:active,
.btn-warning.active {
background-color: #eb9316;
border-color: #e38d13;
}
.btn-warning.disabled,
.btn-warning[disabled],
fieldset[disabled] .btn-warning,
.btn-warning.disabled:hover,
.btn-warning[disabled]:hover,
fieldset[disabled] .btn-warning:hover,
.btn-warning.disabled:focus,
.btn-warning[disabled]:focus,
fieldset[disabled] .btn-warning:focus,
.btn-warning.disabled.focus,
.btn-warning[disabled].focus,
fieldset[disabled] .btn-warning.focus,
.btn-warning.disabled:active,
.btn-warning[disabled]:active,
fieldset[disabled] .btn-warning:active,
.btn-warning.disabled.active,
.btn-warning[disabled].active,
fieldset[disabled] .btn-warning.active {
background-color: #eb9316;
background-image: none;
}
.btn-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #b92c28;
}
.btn-danger:hover,
.btn-danger:focus {
background-color: #c12e2a;
background-position: 0 -15px;
}
.btn-danger:active,
.btn-danger.active {
background-color: #c12e2a;
border-color: #b92c28;
}
.btn-danger.disabled,
.btn-danger[disabled],
fieldset[disabled] .btn-danger,
.btn-danger.disabled:hover,
.btn-danger[disabled]:hover,
fieldset[disabled] .btn-danger:hover,
.btn-danger.disabled:focus,
.btn-danger[disabled]:focus,
fieldset[disabled] .btn-danger:focus,
.btn-danger.disabled.focus,
.btn-danger[disabled].focus,
fieldset[disabled] .btn-danger.focus,
.btn-danger.disabled:active,
.btn-danger[disabled]:active,
fieldset[disabled] .btn-danger:active,
.btn-danger.disabled.active,
.btn-danger[disabled].active,
fieldset[disabled] .btn-danger.active {
background-color: #c12e2a;
background-image: none;
}
.thumbnail,
.img-thumbnail {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-color: #e8e8e8;
}
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-color: #2e6da4;
}
.navbar-default {
background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#f8f8f8));
background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
}
.navbar-default .navbar-nav > .open > a,
.navbar-default .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
}
.navbar-brand,
.navbar-nav > li > a {
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
}
.navbar-inverse {
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222222 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222222));
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
border-radius: 4px;
}
.navbar-inverse .navbar-nav > .open > a,
.navbar-inverse .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
}
.navbar-inverse .navbar-brand,
.navbar-inverse .navbar-nav > li > a {
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.navbar-static-top,
.navbar-fixed-top,
.navbar-fixed-bottom {
border-radius: 0;
}
@media (max-width: 767px) {
.navbar .navbar-nav .open .dropdown-menu > .active > a,
.navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
color: #fff;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
}
}
.alert {
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
}
.alert-success {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
border-color: #b2dba1;
}
.alert-info {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
border-color: #9acfea;
}
.alert-warning {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
border-color: #f5e79e;
}
.alert-danger {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
border-color: #dca7a7;
}
.progress {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
}
.progress-bar {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
}
.progress-bar-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
}
.progress-bar-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
}
.progress-bar-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
}
.progress-bar-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
}
.progress-bar-striped {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
}
.list-group {
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
}
.list-group-item.active,
.list-group-item.active:hover,
.list-group-item.active:focus {
text-shadow: 0 -1px 0 #286090;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
border-color: #2b669a;
}
.list-group-item.active .badge,
.list-group-item.active:hover .badge,
.list-group-item.active:focus .badge {
text-shadow: none;
}
.panel {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.panel-default > .panel-heading {
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
}
.panel-primary > .panel-heading {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
}
.panel-success > .panel-heading {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
}
.panel-info > .panel-heading {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
}
.panel-warning > .panel-heading {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
}
.panel-danger > .panel-heading {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
}
.well {
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
border-color: #dcdcdc;
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<g id="search_1_">
<path fill="#CCCCCC" d="M20,0.005c-6.627,0-12,5.373-12,12c0,2.026,0.507,3.933,1.395,5.608l-8.344,8.342l0.007,0.006
C0.406,26.602,0,27.49,0,28.477c0,1.949,1.58,3.529,3.529,3.529c0.985,0,1.874-0.406,2.515-1.059l-0.002-0.002l8.341-8.34
c1.676,0.891,3.586,1.4,5.617,1.4c6.627,0,12-5.373,12-12S26.627,0.005,20,0.005z M4.795,29.697
c-0.322,0.334-0.768,0.543-1.266,0.543c-0.975,0-1.765-0.789-1.765-1.764c0-0.498,0.21-0.943,0.543-1.266l-0.009-0.008l8.066-8.066
c0.705,0.951,1.545,1.791,2.494,2.498L4.795,29.697z M20,22.006c-5.522,0-10-4.479-10-10c0-5.522,4.478-10,10-10
c5.521,0,10,4.478,10,10C30,17.527,25.521,22.006,20,22.006z"/>
<path fill="#CCCCCC" d="M20,5.005c-3.867,0-7,3.134-7,7c0,0.276,0.224,0.5,0.5,0.5s0.5-0.224,0.5-0.5c0-3.313,2.686-6,6-6
c0.275,0,0.5-0.224,0.5-0.5S20.275,5.005,20,5.005z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,252 @@
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,300');
/* Have to use @import for the font, as you can only specify a single stylesheet */
* {
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html {
min-height: 100%;
border-top: 10px solid #ECEEF1;
border-bottom: 10px solid #ECEEF1;
color: #61666c;
font-weight: 300;
font-size: 1em;
font-family: 'Open Sans', sans-serif;
line-height: 2em;
}
body {
padding: 20px;
-webkit-backface-visibility: hidden;
}
code {
font-family: Inconsolata,monospace;
}
a {
color: #61666c;
text-decoration: none;
}
a:hover {
color: #2a2a2a;
}
/*------------------------------------*\
Wrapper
\*------------------------------------*/
.wrapper {
margin: 0 auto;
padding-top: 20px;
max-width: 800px;
}
/*------------------------------------*\
Demo block
\*------------------------------------*/
.block {
font-size: .875em;
margin: 20px 0;
padding: 20px;
color: #9099A3;
}
h1 {
font-weight: 200;
text-align: center;
font-size: 1.4em;
line-height: 3em;
font-family: 'Museo Slab', 'Open Sans', monospace;
}
form {
text-align: center;
}
input {
margin: 0 auto;
font-size: 100%;
vertical-align: middle;
*overflow: visible;
line-height: normal;
font-family: 'Open Sans', sans-serif;
font-size: 12px;
font-weight: 300;
line-height: 18px;
display: inline-block;
height: 20px;
padding: 4px 32px 4px 6px;
margin-bottom: 9px;
font-size: 14px;
line-height: 20px;
color: #555555;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
width: 196px;
background-color: #ffffff;
border: 1px solid #cccccc;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
-moz-transition: border linear 0.2s, box-shadow linear 0.2s;
-o-transition: border linear 0.2s, box-shadow linear 0.2s;
transition: border linear 0.2s, box-shadow linear 0.2s;
background: url('search.svg') no-repeat 211px center;
background-size: auto 20px;
}
input:focus {
border-color: rgba(82, 168, 236, 0.8);
outline: 0;
outline: thin dotted \9;
/* IE6-9 */
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
input::-moz-focus-inner {
padding: 0;
border: 0;
}
input[type="search"] {
margin-top: 20px;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
-webkit-appearance: textfield;
-webkit-transition: all 300ms ease-in;
-moz-transition: all 300ms ease-in;
-ms-transition: all 300ms ease-in;
-o-transition: all 300ms ease-in;
transition: all 300ms ease-in;
}
input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none;
}
a.clear,
a.clear:link,
a.clear:visited {
color: #666;
padding: 2px 0 2px 0;
font-weight: 400;
font-size: 14px;
margin: 0px 0 0 20px;
line-height: 14px;
display: inline-block;
border-bottom: transparent 1px solid;
vertical-align: -10px;
-webkit-transition: all 300ms ease-in;
-moz-transition: all 300ms ease-in;
-ms-transition: all 300ms ease-in;
-o-transition: all 300ms ease-in;
transition: all 300ms ease-in;
}
a.clear:hover {
text-decoration: none;
color: #333;
cursor: pointer;
}
/*------------------------------------*\
Table (directory listing)
\*------------------------------------*/
table {
border-collapse: collapse;
font-size: .875em;
max-width: 100%;
margin: 20px auto 0px auto;
}
tr {
outline: 0;
border: 0;
}
tr:hover td {
background: #f6f6f6;
}
th {
text-align: left;
font-size: .75em;
padding-right: 20px;
}
/* 2nd Column: Filename */
th + th {
width: 65%;
}
/* 3rd Column: Last Modified */
/* 4th Column: Size */
th + th + th + th {
width: 5%;
}
tr td:first-of-type {
padding-left: 10px;
padding-right: 10px;
}
td {
padding: 5px 0;
outline: 0;
border: 0;
border-bottom: 1px solid #edf1f5;
vertical-align: middle;
text-align: left;
-webkit-transition: background 300ms ease-in;
-moz-transition: background 300ms ease-in;
-ms-transition: background 300ms ease-in;
-o-transition: background 300ms ease-in;
transition: background 300ms ease-in;
}
td:last-child,
th:last-child {
text-align: right;
padding-right: 5px;
}
td a {
display: block;
}
tr.parent a {
color: #9099A3;
}
.parent a:hover {
color: #2a2a2a;
}
/*------------------------------------*\
Loading Indicator
\*------------------------------------*/
.signal {
border: 2px solid #333;
border-radius: 15px;
height: 15px;
left: 50%;
margin: -8px 0 0 -8px;
opacity: 0;
top: 50%;
width: 15px;
float: right;
animation: pulsate 1s ease-out;
animation-iteration-count: infinite;
}
@keyframes pulsate {
0% {
transform: scale(.1);
opacity: 0.0;
}
50% {
opacity: 1;
}
100% {
transform: scale(1.2);
opacity: 0;
}
}
/*------------------------------------*\
Footer
\*------------------------------------*/
.footer {
text-align: center;
font-size: .75em;
margin-top: 50px;
}
img {
outline: none;
border: none;
max-height: 16px;
}

View file

@ -0,0 +1,226 @@
@import url('http://fonts.googleapis.com/css?family=Open+Sans:400,300');
/* Have to use @import for the font, as you can only specify a single stylesheet */
* {
margin:0;
padding:0;
-webkit-box-sizing:border-box;
-moz-box-sizing:border-box;
box-sizing: border-box;
}
html {
min-height:100%;
border-top:10px solid #ECEEF1;
border-bottom:10px solid #ECEEF1;
color:#61666c;
font-weight:300;
font-size:1em;
font-family:'Open Sans', sans-serif;
line-height:2em;
}
body {
padding:20px;
-webkit-backface-visibility:hidden;
}
code {
font-family:Inconsolata,monospace;
}
a {
color:#61666c;
text-decoration:none;
}
a:hover {
color:#2a2a2a;
}
/*------------------------------------*\
Wrapper
\*------------------------------------*/
.wrapper {
margin:0 auto;
padding-top:20px;
max-width:800px;
}
/*------------------------------------*\
Demo block
\*------------------------------------*/
.block {
font-size:.875em;
margin:20px 0;
padding:20px;
color:#9099A3;
}
h1 {
font-weight:200;
text-align:center;
font-size:1.4em;
line-height:3em;
font-family:'Museo Slab','Open Sans',monospace;
}
form {
text-align:center;
}
input {
margin: 0 auto;
font-size: 100%;
vertical-align: middle;
*overflow: visible;
line-height: normal;
font-family:'Open Sans', sans-serif;
font-size: 12px;
font-weight: 300;
line-height: 18px;
color:#555555;
display: inline-block;
height: 20px;
padding: 4px 32px 4px 6px;
margin-bottom: 9px;
font-size: 14px;
line-height: 20px;
color: #555555;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
width: 196px;
background-color: #ffffff;
border: 1px solid #cccccc;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-webkit-transition: border linear .2s, box-shadow linear .2s;
-moz-transition: border linear .2s, box-shadow linear .2s;
-o-transition: border linear .2s, box-shadow linear .2s;
transition: border linear .2s, box-shadow linear .2s;
background: url('search.svg') no-repeat 211px center;
background-size:auto 20px;
}
input:focus {
border-color: rgba(82, 168, 236, 0.8);
outline: 0;
outline: thin dotted \9;
/* IE6-9 */
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
input::-moz-focus-inner {
padding: 0;
border: 0;
}
input[type="search"] {
margin-top: 20px;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
-webkit-appearance: textfield;
-webkit-transition:all 300ms ease-in;
-moz-transition:all 300ms ease-in;
-ms-transition:all 300ms ease-in;
-o-transition:all 300ms ease-in;
transition:all 300ms ease-in;
}
input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none;
}
a.clear, a.clear:link, a.clear:visited {
color:#666;
padding:2px 0 2px 0;
font-weight: 400;
font-size: 14px;
margin:0px 0 0 20px;
line-height: 14px;
display: inline-block;
border-bottom: transparent 1px solid;
vertical-align: -10px;
-webkit-transition:all 300ms ease-in;
-moz-transition:all 300ms ease-in;
-ms-transition:all 300ms ease-in;
-o-transition:all 300ms ease-in;
transition:all 300ms ease-in;
}
a.clear:hover {
text-decoration: none;
color:#333;
cursor: pointer;
}
/*------------------------------------*\
Table (directory listing)
\*------------------------------------*/
table {
border-collapse:collapse;
font-size:.875em;
max-width:100%;
margin:20px auto 0px auto;
}
tr {
outline:0;
border:0;
}
tr:hover td {
background:#f6f6f6;
}
th {
text-align:left;
font-size:.75em;
padding-right:20px;
}
/* 2nd Column: Filename */
th + th {
width:65%;
}
/* 3rd Column: Last Modified */
th + th + th {
}
/* 4th Column: Size */
th + th + th + th {
width:5%;
}
tr td:first-of-type {
padding-left:10px;
padding-right:10px;
}
td {
padding:5px 0;
outline:0;
border:0;
border-bottom:1px solid #edf1f5;
vertical-align:middle;
text-align:left;
-webkit-transition:background 300ms ease-in;
-moz-transition:background 300ms ease-in;
-ms-transition:background 300ms ease-in;
-o-transition:background 300ms ease-in;
transition:background 300ms ease-in;
}
td:last-child, th:last-child {
text-align:right;
padding-right:0px;
}
td a{
display: block;
}
tr.parent a {
color:#9099A3;
}
.parent a:hover {
color:#2a2a2a;
}
/*------------------------------------*\
Footer
\*------------------------------------*/
.footer {
text-align:center;
font-size:.75em;
margin-top:50px;
}
img {
outline:none;
border:none;
max-height:16px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,103 @@
// pretty date function
function prettyDate(time){
var date = new Date((time || "").replace(/-/g,"/").replace(/[TZ]/g," ")),
diff = (((new Date()).getTime() - date.getTime()) / 1000),
day_diff = Math.floor(diff / 86400);
if (isNaN(day_diff) || day_diff < 0)
return;
return day_diff == 0 && (
diff < 60 && "just now" ||
diff < 120 && "1 minute ago" ||
diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
diff < 7200 && "1 hour ago" ||
diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
day_diff == 1 && "Yesterday" ||
day_diff < 7 && day_diff + " days ago" ||
day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago" ||
day_diff > 31 && Math.round(day_diff / 31) + " months ago";
}
// search function
function search(search_val){
var suche = search_val.toLowerCase();
var table = document.getElementById("directory");
var cellNr = 1;
var ele;
for (var r = 1; r < table.rows.length; r++){
ele = table.rows[r].cells[cellNr].innerHTML.replace(/<[^>]+>/g,"");
if (ele.toLowerCase().indexOf(suche)>=0 ) {
table.rows[r].style.display = '';
} else {
table.rows[r].style.display = 'none';
}
}
}
function loadVulnerabilityCount(url){
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
if (xhr.status === 200) {
var report = JSON.parse(xhr.responseText);
var id = report.Repo + ':' + report.Tag;
var element = document.getElementById(id);
if (element) {
element.innerHTML = report.BadVulns;
} else {
console.log("element not found for given id ", id);
}
}
};
xhr.send();
}
var el = document.querySelectorAll('tr:nth-child(2)')[0].querySelectorAll('td:nth-child(2)')[0];
if (el.textContent == 'Parent Directory'){
var parent_row = document.querySelectorAll('tr:nth-child(2)')[0];
if (parent_row.classList){
parent_row.classList.add('parent');
} else {
parent_row.className += ' ' + 'parent';
}
}
var cells = document.querySelectorAll('td a');
Array.prototype.forEach.call(cells, function(item, index){
var link = item.getAttribute('href');
link = link.replace('.html', '');
item.setAttribute('href', link);
});
var our_table = document.querySelectorAll('table')[0];
our_table.setAttribute('id', 'directory');
// search script
var search_input = document.querySelectorAll('input[name="filter"]')[0];
var clear_button = document.querySelectorAll('a.clear')[0];
if (search_input) {
if (search_input.value !== ''){
search(search_input.value);
}
search_input.addEventListener('keyup', function(e){
e.preventDefault();
search(search_input.value);
});
search_input.addEventListener('keypress', function(e){
if ( e.which == 13 ) {
e.preventDefault();
}
});
}
if (clear_button) {
clear_button.addEventListener('click', function(e){
search_input.value = '';
search('');
});
}

View file

@ -0,0 +1,54 @@
{{define "repositories"}}
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<base href="/" >
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{ .RegistryDomain }}</title>
<link rel="icon" type="image/ico" href="/static/favicon.ico">
<link rel="stylesheet" href="/static/css/styles.css" />
</head>
<body>
<h1>{{ .RegistryDomain }}</h1>
<form>
<input name="filter" type="search"><a class="clear">clear</a>
</form>
<div class="wrapper">
<table>
<tr>
<th>Repository Name</th>
<th>Pull Command</th>
</tr>
{{ range $key, $value := .Repositories }}
<tr>
<td valign="top">
<a href="/repo/{{ $value.Name | urlquery }}/tags">
{{ $value.Name }}
</a>
</td>
<td align="right" nowrap>
<a href="/repo/{{ $value.Name | urlquery }}/tags">
<code>docker pull {{ $value.URI }}</code>
</a>
</td>
</tr>
{{ end }}
</table>
</div>
<div class="footer">
<p>Made with <code><3</code> by <a href="https://github.com/jessfraz">@jessfraz</a></p>
<p>Checkout the source code at: <a href="https://github.com/genuinetools/reg">github.com/genuinetools/reg</a></p>
<p>Last Updated: {{ .LastUpdated }}</p>
<p>Update Interval: {{ .UpdateInterval }}</p>
</div><!--/.footer-->
<script src="/static/js/scripts.js"></script>
</body>
</html>
{{end}}

View file

@ -0,0 +1,74 @@
{{define "tags"}}
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<base href="/" >
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{ .RegistryDomain }}/{{ .Name }}</title>
<link rel="icon" type="image/ico" href="/static/favicon.ico">
<link rel="stylesheet" href="/static/css/styles.css" />
</head>
<body>
<h1>{{ .RegistryDomain }}/{{ .Name }}</h1>
<div class="wrapper">
<table>
<tr>
<th>Name</th>
<th>Tag</th>
<th>Created</th>
{{if .HasVulns}}<th>Vulnerabilities</th>{{end}}
</tr>
{{ range $key, $value := .Repositories }}
<tr>
<td valign="left" nowrap>
{{if $.HasVulns}}<a href="/repo/{{ $value.Name | urlquery }}/tag/{{ $value.Tag }}/vulns">{{end}}
{{ $value.Name }}
{{if $.HasVulns}}</a>{{end}}
</td>
<td align="right" nowrap>
{{if $.HasVulns}}<a href="/repo/{{ $value.Name | urlquery }}/tag/{{ $value.Tag }}/vulns">{{end}}
{{ $value.Tag }}
{{if $.HasVulns}}</a>{{end}}
</td>
<td align="right" nowrap>
{{ $value.Created.Format "02 Jan, 2006 15:04:05 UTC" }}
</td>
{{if $.HasVulns}}
<td align="right" nowrap>
<a href="/repo/{{ $value.Name | urlquery }}/tag/{{ $value.Tag }}/vulns" id="{{ $value.Name }}:{{ $value.Tag }}">
<div class="signal"></div>
</a>
</td>
{{end}}
</tr>
{{ end }}
</table>
</div>
<div class="footer">
<p>Made with <code><3</code> by <a href="https://github.com/jessfraz">@jessfraz</a></p>
<p>Checkout the source code at: <a href="https://github.com/genuinetools/reg">github.com/genuinetools/reg</a></p>
<p>Last Updated: {{ .LastUpdated }}</p>
</div><!--/.footer-->
<script src="/static/js/scripts.js"></script>
{{if .HasVulns}}
<script type="text/javascript">
var ajaxCalls = [
{{ range $key, $value := .Repositories }}
'/repo/{{ $value.Name | urlquery }}/tag/{{ $value.Tag }}/vulns.json',
{{ end }}
];
window.onload = function() {
Array.prototype.forEach.call(ajaxCalls, function(url, index){
loadVulnerabilityCount(url);
});
};
</script>
{{end}}
</body>
</html>
{{end}}

View file

@ -0,0 +1,73 @@
{{define "vulns"}}
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<base href="/" >
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{ .RegistryURL }}/{{ .Repo }}:{{ .Tag }} Vulnerability Report</title>
<link rel="icon" type="image/ico" href="/static/favicon.ico">
<link rel="stylesheet" href="/static/css/bootstrap.min.css" />
</head>
<body>
<div class="container">
<ol class="breadcrumb">
<li><a href="/">{{ .RegistryURL }}</a></li>
<li class="active">{{ .Repo }}:{{ .Tag }}</li>
</ol>
<div class="page-header">
<h1>{{ .RegistryURL }}/{{ .Repo }}:{{ .Tag }} <small>Vulnerability Report</small></h1>
</div>
<p class="text-right">Generated on: {{.Date}}</p>
{{if gt .BadVulns 5}}
<div class="alert alert-danger" role="alert">
{{.BadVulns}} High, Critical, and/or Defcon1 vulnerabilities found
</div>
{{end}}
<h2>Summary</h2>
<ul class="list-group">
<li class="list-group-item">
<span class="badge">{{ len .Vulns }}</span>
<b>Total</b>
</li>
{{range $key, $value := .VulnsBySeverity}}
<li class="list-group-item">
<span class="label label-{{color $key}} pull-right">{{ len $value }}</span>
{{ $key }}
</li>
{{end}}
</ul>
<h2>Details</h2>
{{range $vulns := .VulnsBySeverity}}
{{range $value := $vulns}}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{$value.Name}}
<span class="label label-{{color $value.Severity}} pull-right">{{$value.Severity}}</span>
</h3>
</div>
<div class="panel-body">
{{$value.Description}}
</div>
<div class="panel-footer">
<a href="{{$value.Link}}" target="_blank">{{$value.Link}}</a>
</div>
</div>
{{end}}
{{end}}
<footer class="text-center">
<p>Made with <code><3</code> by <a href="https://github.com/jessfraz">@jessfraz</a></p>
<p>Checkout the source code at: <a href="https://github.com/genuinetools/reg">github.com/genuinetools/reg</a></p>
</footer>
</div>
</body>
</html>
{{end}}

BIN
vendor/github.com/genuinetools/reg/server/vuln.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View file

@ -1,28 +1,51 @@
package main
import (
"context"
"flag"
"fmt"
"sort"
"strings"
"github.com/urfave/cli"
"github.com/genuinetools/reg/registry"
)
var tagsCommand = cli.Command{
Name: "tags",
Usage: "get the tags for a repository",
Action: func(c *cli.Context) error {
if len(c.Args()) < 1 {
return fmt.Errorf("pass the name of the repository")
}
const tagsHelp = `Get the tags for a repository.`
tags, err := r.Tags(c.Args()[0])
if err != nil {
return err
}
func (cmd *tagsCommand) Name() string { return "tags" }
func (cmd *tagsCommand) Args() string { return "[OPTIONS] NAME[:TAG|@DIGEST]" }
func (cmd *tagsCommand) ShortHelp() string { return tagsHelp }
func (cmd *tagsCommand) LongHelp() string { return tagsHelp }
func (cmd *tagsCommand) Hidden() bool { return false }
// Print the tags.
fmt.Println(strings.Join(tags, "\n"))
func (cmd *tagsCommand) Register(fs *flag.FlagSet) {}
return nil
},
type tagsCommand struct{}
func (cmd *tagsCommand) Run(ctx context.Context, args []string) error {
if len(args) < 1 {
return fmt.Errorf("pass the name of the repository")
}
image, err := registry.ParseImage(args[0])
if err != nil {
return err
}
// Create the registry client.
r, err := createRegistryClient(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"))
return nil
}

View file

@ -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)
}
}

View file

@ -0,0 +1,23 @@
version: 0.1
log:
level: debug
formatter: text
fields:
service: registry
storage:
filesystem:
rootdirectory: /var/lib/registry
delete:
enabled: true
http:
addr: 0.0.0.0:5000
headers:
X-Content-Type-Options: [nosniff]
host: https://localhost:5000
tls:
certificate: /etc/docker/registry/ssl/cert.pem
key: /etc/docker/registry/ssl/key.pem
auth:
htpasswd:
realm: basic-realm
path: /etc/docker/registry/htpasswd

View file

@ -0,0 +1,75 @@
# Copyright 2015 clair authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The values specified here are the default values that Clair uses if no configuration file is specified or if the keys are not defined.
clair:
database:
# Database driver
type: pgsql
options:
# PostgreSQL Connection string
# https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
source: postgresql://hacker:password@127.0.0.1:5432/clair?sslmode=disable&statement_timeout=60000
# Number of elements kept in the cache
# Values unlikely to change (e.g. namespaces) are cached in order to save prevent needless roundtrips to the database.
cachesize: 16384
api:
# API server port
addr: "0.0.0.0:6060"
# Health server port
# This is an unencrypted endpoint useful for load balancers to check to healthiness of the clair server.
healthaddr: "0.0.0.0:6061"
# Deadline before an API request will respond with a 503
timeout: 900s
# Optional PKI configuration
# If you want to easily generate client certificates and CAs, try the following projects:
# https://github.com/coreos/etcd-ca
# https://github.com/cloudflare/cfssl
servername:
cafile:
keyfile:
certfile:
updater:
# Frequency the database will be updated with vulnerabilities from the default data sources
# The value 0 disables the updater entirely.
interval: 2h
notifier:
# Number of attempts before the notification is marked as failed to be sent
attempts: 3
# Duration before a failed notification is retried
renotifyinterval: 2h
http:
# Optional endpoint that will receive notifications via POST requests
endpoint:
# Optional PKI configuration
# If you want to easily generate client certificates and CAs, try the following projects:
# https://github.com/cloudflare/cfssl
# https://github.com/coreos/etcd-ca
servername:
cafile:
keyfile:
certfile:
# Optional HTTP Proxy: must be a valid URL (including the scheme).
proxy:

View file

@ -0,0 +1 @@
admin:$2y$05$TpcLzA8b5hMLptNGRIRWXuOI7KmAOqIuRhHAv15qHNrJaxuyIhCg6

View file

@ -0,0 +1,19 @@
version: 0.1
log:
level: debug
formatter: text
fields:
service: registry
storage:
filesystem:
rootdirectory: /var/lib/registry
delete:
enabled: true
http:
addr: 0.0.0.0:5000
headers:
X-Content-Type-Options: [nosniff]
host: https://localhost:5000
tls:
certificate: /etc/docker/registry/ssl/cert.pem
key: /etc/docker/registry/ssl/key.pem

View file

@ -0,0 +1,50 @@
package testutils
import (
"context"
"os"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/term"
)
// RegistryHelper implements methods to manipulate docker registry from test cases
type RegistryHelper struct {
dcli *client.Client
auth string
addr string
}
// NewRegistryHelper returns RegistryHelper
func NewRegistryHelper(dcli *client.Client, username, password, addr string) (*RegistryHelper, error) {
auth, err := constructRegistryAuth(username, password)
if err != nil {
return nil, err
}
return &RegistryHelper{dcli: dcli, auth: auth, addr: addr}, nil
}
// RefillRegistry adds images to a registry.
func (r *RegistryHelper) RefillRegistry(image string) error {
if err := pullDockerImage(r.dcli, image); err != nil {
return err
}
if err := r.dcli.ImageTag(context.Background(), image, r.addr+"/"+image); err != nil {
return err
}
resp, err := r.dcli.ImagePush(context.Background(), r.addr+"/"+image, types.ImagePushOptions{
RegistryAuth: r.auth,
})
if err != nil {
return err
}
defer resp.Close()
fd, isTerm := term.GetFdInfo(os.Stdout)
return jsonmessage.DisplayJSONMessagesStream(resp, os.Stdout, fd, isTerm, nil)
}

View file

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDAzCCAeugAwIBAgIRAKiO1JljWQroJypQxJGTyQYwDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xODA2MDYxNjE4MzFaFw0xOTA2MDYxNjE4
MzFaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDIjs9fg/lVbozhiYuu7kswhXGTdLlbvRTpfCX/wNpeVi8AIWRLetsO
Q/L/Os3rz+3ejozFREZViqJ5hDODgzMCP93M7lAZhzhrgxwe8+ZYPhj3WHqAc+TK
fnW0SNa+gOYPDo98xx1eWQKSLZ8Qj+NjkbBnUu92/zkSKFIvR6RFZwRxrItGZ003
OtsSwDn5yVGfcbtJyXMrR1669LvInIRlz9ekumG7cGi8odSKl3xErmGTZYEzozeP
MuJLDVM0fh5oOOucsn1sJJXqTNzFXQqvmnXJp62jop9tJcioeyDJvqwWjyVQpwIN
ZiqGFFu8LDvba4t+PVwsqrMoUCWEq1yjAgMBAAGjVDBSMA4GA1UdDwEB/wQEAwIC
pDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MBoGA1UdEQQT
MBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAbwh3lknBACmn
lAm5e/OUpFiGEwV3otRR7tLiQFFPGtQ3hI8kHCjWplDyA9dCiPrH3z84cV/fH5sK
4SfG40ucO4r9BVof7dnahl4dejafymxlicD87zUsWRJFIgAlJlpVBkjv9Xd2Fupr
PWpbT8fHxeaZ5+Q5vvF7j2qCv+8i3GNgVVFC86HFc0DHRT78a2EW9YkWKdkFUh1W
zvuBDwoYv4rVKlhyhrPHMIHGBwdb0meRghqRilSR0qOnLy3fyrKQYpB4WAsBRl+/
dFYS9krqSOeqhIZTYkQzI6wlxexZ6vcb7NRhxX33BaFQNmsJnW9oZ2sHkpJ/Ceoe
7QaSRcCbtQ==
-----END CERTIFICATE-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAyI7PX4P5VW6M4YmLru5LMIVxk3S5W70U6Xwl/8DaXlYvACFk
S3rbDkPy/zrN68/t3o6MxURGVYqieYQzg4MzAj/dzO5QGYc4a4McHvPmWD4Y91h6
gHPkyn51tEjWvoDmDw6PfMcdXlkCki2fEI/jY5GwZ1Lvdv85EihSL0ekRWcEcayL
RmdNNzrbEsA5+clRn3G7SclzK0deuvS7yJyEZc/XpLphu3BovKHUipd8RK5hk2WB
M6M3jzLiSw1TNH4eaDjrnLJ9bCSV6kzcxV0Kr5p1yaeto6KfbSXIqHsgyb6sFo8l
UKcCDWYqhhRbvCw722uLfj1cLKqzKFAlhKtcowIDAQABAoIBABrnzboqolBrvEql
gS++mCeyP0Jg7lz4SM3p8c8VuDXfqf6CfEoD+U84nmjPIsD37BlnVktAlY70Qke7
DI4gE7/Bgaf0cJp8IX2K4ULlxYkhC4hjPkvtQExKGtBM1UJJWJO8iFNdAvudRVMG
+8flBcRdzySNY8K7CpT9A8mD+u20X1n5yQt4wiBlda2IqEgDmTOJ1pxq99bR+nVu
Z1jglgKvVgSuL0JFR3flT2zcRZlRiF3LESfuTy46VXdRGm5wX8rlQaxntsq3BCIs
d5+Sy0ajnESZlr4FlHN4h0aNpchntLGKtU5lmyraomRHiD2pxJ3ZM+A5veDVGx9J
Y+hJ+gECgYEA0zhPogEmIYVNl1l9LA2Hz2GXvS2hNCswLL/lxUXSrFrbdpl0lXGE
d2hgnIFA8CjdMj3P/3VTY71SiDYROEuGbOB87CRpDGdmkpFueIrxbgW92divamVj
7gIV9Jdm4hKKALANPDF3JeF7j4M1nNDnRJPQl8KR2x7c7LniVvJ3fAMCgYEA8xPT
n0aZhkZSLueqT1v0IzpE2Vs82o0y4LPBBaxA6Hx5IkQ4Z+D3ifyH0baJfUVOOV7N
NbfOGzMzZ0+6IelaC5UZCl6fiSxn2/IgByr52tEHJv/wthfqw1Lskxp8ebS3bszI
/ZaZkZ4e3TKRC6M7lP2YG0qH626emzWxf8nvyuECgYATLfbVMCOFQbSE/MRH/saJ
R0RfEkikExPhzF8R5cA2lF1/THnwpAkySpETRQ1fgWZsjH5ZpQ64bNWUtswjf4Aq
XMwbKUc8sBr5Tilo2r0Hj4/ouytajvBfCWNy/ViDSMmtPE9HWvqFvw7YPkLdBX8q
k/2J+koCSrAm8s4htQyyfwKBgQCRcuirIsSUqxlcBbVL/TrNpX0zDjwQjnLGL+ks
6tCADJMzJN0Xk26re5cNrosAkWroO0jRfuVuMynsBLHcvtPpoFK2eL4/h3myC2SM
xXNyMqdz96viWddY/xKeRzf6X19vhkwyKV5E2vee5jYSX580XLYahnNkNfHj77IB
RCUfIQKBgQDHtgkK1nRyh3Vv7IpFFGXVOd4HiJ12nWFyXpS1mjzdQ5ARIrEFNz2Z
y13bfrfjbEkVWXeCDBzkICWMnRBDtQc1hb6APfodUlakpLyxvFCA5SzygXvrK+MK
aDPwY39N3pu0mvfM2Zs2LEXnAIp9VUvWCP+S4GvmHKbp+2tr6PtgkQ==
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,414 @@
package testutils
import (
"archive/tar"
"bufio"
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/term"
)
// StartRegistry starts a new registry container.
func StartRegistry(dcli *client.Client, config, username, password string) (string, string, error) {
_, filename, _, ok := runtime.Caller(0)
if !ok {
return "", "", errors.New("No caller information")
}
image := "registry:2"
if err := pullDockerImage(dcli, image); err != nil {
return "", "", err
}
r, err := dcli.ContainerCreate(
context.Background(),
&container.Config{
Image: image,
},
&container.HostConfig{
NetworkMode: "host",
Binds: []string{
filepath.Join(filepath.Dir(filename), "configs", config) + ":" + "/etc/docker/registry/config.yml" + ":ro",
filepath.Join(filepath.Dir(filename), "configs", "htpasswd") + ":" + "/etc/docker/registry/htpasswd" + ":ro",
filepath.Join(filepath.Dir(filename), "snakeoil") + ":" + "/etc/docker/registry/ssl" + ":ro",
},
RestartPolicy: container.RestartPolicy{
Name: "always",
},
},
nil, "")
if err != nil {
return "", "", err
}
// start the container
if err := dcli.ContainerStart(context.Background(), r.ID, types.ContainerStartOptions{}); err != nil {
return r.ID, "", err
}
port := ":5000"
addr := "https://localhost" + port
if err := waitForConn(addr, filepath.Join(filepath.Dir(filename), "snakeoil", "cert.pem"), filepath.Join(filepath.Dir(filename), "snakeoil", "key.pem")); err != nil {
return r.ID, addr, err
}
if err := dockerLogin("localhost"+port, username, password); err != nil {
return r.ID, addr, err
}
// Prefill the images.
images := []string{"alpine:3.5", "alpine:latest", "busybox:latest", "busybox:musl", "busybox:glibc"}
for _, image := range images {
if err := prefillRegistry(image, dcli, "localhost"+port, username, password); err != nil {
return r.ID, addr, err
}
}
return r.ID, addr, nil
}
func startClairDB(dcli *client.Client) (string, error) {
image := "postgres:latest"
if err := pullDockerImage(dcli, image); err != nil {
return "", err
}
c, err := dcli.ContainerCreate(
context.Background(),
&container.Config{
Image: image,
Env: []string{
"POSTGRES_PASSWORD=password",
"POSTGRES_DB=clair",
"POSTGRES_USER=hacker",
},
},
&container.HostConfig{
NetworkMode: "host",
RestartPolicy: container.RestartPolicy{
Name: "always",
},
},
nil, "")
if err != nil {
return "", err
}
// start the container
return c.ID, dcli.ContainerStart(context.Background(), c.ID, types.ContainerStartOptions{})
}
// StartClair starts a new clair container and accompanying database.
func StartClair(dcli *client.Client) (string, string, error) {
_, filename, _, ok := runtime.Caller(0)
if !ok {
return "", "", errors.New("No caller information")
}
// start the database container.
dbID, err := startClairDB(dcli)
if err != nil {
return dbID, "", err
}
image := "clair:dev"
// build the docker image
// create the tar ball
ctx := filepath.Dir(filepath.Dir(filename))
tw, err := tarit(ctx)
if err != nil {
return dbID, "", fmt.Errorf("tarit: %v", err)
}
// build the image
resp, err := dcli.ImageBuild(context.Background(), tw, types.ImageBuildOptions{
Tags: []string{image},
Dockerfile: "Dockerfile.clair",
ForceRemove: true,
Remove: true,
SuppressOutput: false,
PullParent: true,
})
if err != nil {
return dbID, "", err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return dbID, "", err
}
fmt.Printf("body: %s\n", string(b))
c, err := dcli.ContainerCreate(
context.Background(),
&container.Config{
Image: image,
},
&container.HostConfig{
NetworkMode: "host",
Binds: []string{
filepath.Join(filepath.Dir(filename), "configs", "clair.yml") + ":" + "/etc/clair/config.yaml" + ":ro",
},
RestartPolicy: container.RestartPolicy{
Name: "always",
},
},
nil, "")
if err != nil {
return dbID, c.ID, err
}
// start the container
err = dcli.ContainerStart(context.Background(), c.ID, types.ContainerStartOptions{})
// wait for clair to start
// TODO: make this not a sleep
time.Sleep(time.Second * 5)
return dbID, c.ID, err
}
// RemoveContainer removes with force a container by it's container ID.
func RemoveContainer(dcli *client.Client, ctrs ...string) (err error) {
for _, c := range ctrs {
err = dcli.ContainerRemove(context.Background(), c,
types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
})
}
return err
}
// dockerLogin logins via the command line to a docker registry
func dockerLogin(addr, username, password string) error {
cmd := exec.Command("docker", "login", "--username", username, "--password", password, addr)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("docker login [%s] failed with output %q and error: %v", strings.Join(cmd.Args, " "), string(out), err)
}
return nil
}
// prefillRegistry adds images to a registry.
func prefillRegistry(image string, dcli *client.Client, addr, username, password string) error {
if err := pullDockerImage(dcli, image); err != nil {
return err
}
if err := dcli.ImageTag(context.Background(), image, addr+"/"+image); err != nil {
return err
}
auth, err := constructRegistryAuth(username, password)
if err != nil {
return err
}
resp, err := dcli.ImagePush(context.Background(), addr+"/"+image, types.ImagePushOptions{
RegistryAuth: auth,
})
if err != nil {
return err
}
defer resp.Close()
fd, isTerm := term.GetFdInfo(os.Stdout)
return jsonmessage.DisplayJSONMessagesStream(resp, os.Stdout, fd, isTerm, nil)
}
func pullDockerImage(dcli *client.Client, image string) error {
exists, err := imageExists(dcli, image)
if err != nil {
return err
}
if exists {
return nil
}
resp, err := dcli.ImagePull(context.Background(), image, types.ImagePullOptions{})
if err != nil {
return err
}
defer resp.Close()
fd, isTerm := term.GetFdInfo(os.Stdout)
return jsonmessage.DisplayJSONMessagesStream(resp, os.Stdout, fd, isTerm, nil)
}
func imageExists(dcli *client.Client, image string) (bool, error) {
_, _, err := dcli.ImageInspectWithRaw(context.Background(), image)
if err == nil {
return true, nil
}
if client.IsErrNotFound(err) {
return false, nil
}
return false, err
}
// waitForConn takes a tcp addr and waits until it is reachable
func waitForConn(addr, cert, key string) error {
tlsCert, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
return fmt.Errorf("Could not load X509 key pair: %v. Make sure the key is not encrypted", err)
}
certPool, err := x509.SystemCertPool()
if err != nil {
return fmt.Errorf("failed to read system certificates: %v", err)
}
pem, err := ioutil.ReadFile(cert)
if err != nil {
return fmt.Errorf("could not read CA certificate %s: %v", cert, err)
}
if !certPool.AppendCertsFromPEM(pem) {
return fmt.Errorf("failed to append certificates from PEM file: %s", cert)
}
c := http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{
Certificates: []tls.Certificate{tlsCert},
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
RootCAs: certPool,
},
},
}
n := 0
max := 10
for n < max {
if _, err := c.Get(addr + "/v2/"); err != nil {
fmt.Printf("try number %d to %s: %v\n", n, addr, err)
n++
if n != max {
fmt.Println("sleeping for 1 second then will try again...")
time.Sleep(time.Second)
} else {
return fmt.Errorf("[WHOOPS]: maximum retries for %s exceeded", addr)
}
continue
} else {
break
}
}
return nil
}
// constructRegistryAuth serializes the auth configuration as JSON base64 payload.
func constructRegistryAuth(identity, secret string) (string, error) {
buf, err := json.Marshal(types.AuthConfig{Username: identity, Password: secret})
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(buf), nil
}
func tarit(src string) (io.Reader, error) {
s := bytes.NewBuffer(nil)
t := bytes.NewBuffer(nil)
buf := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(t))
tarball := tar.NewWriter(s)
err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
var link string
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
if link, err = os.Readlink(path); err != nil {
return err
}
}
header, err := tar.FileInfoHeader(info, link)
if err != nil {
return err
}
header.Name = strings.TrimPrefix(path, src)
if err := tarball.WriteHeader(header); err != nil {
return err
}
if info.IsDir() {
return nil
}
if !info.Mode().IsRegular() { //nothing more to do for non-regular
return nil
}
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(tarball, file)
return err
})
if err != nil {
return nil, err
}
if _, err := s.WriteTo(buf); err != nil {
return nil, err
}
err = buf.Writer.Flush()
return t, err
}

View file

@ -0,0 +1,7 @@
package version
// VERSION indicates which version of the binary is running.
var VERSION string
// GITCOMMIT indicates which git hash the binary was built off of
var GITCOMMIT string

View file

@ -1,103 +1,127 @@
package main
import (
"context"
"errors"
"flag"
"fmt"
"os"
"github.com/genuinetools/reg/clair"
"github.com/genuinetools/reg/repoutils"
"github.com/genuinetools/reg/registry"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
var vulnsCommand = cli.Command{
Name: "vulns",
Usage: "get a vulnerability report for the image from CoreOS Clair",
Flags: []cli.Flag{
cli.StringFlag{
Name: "clair",
Usage: "url to clair instance",
},
cli.IntFlag{
Name: "fixable-threshold",
Usage: "number of fixable issues permitted",
Value: 0,
},
},
Action: func(c *cli.Context) error {
if c.String("clair") == "" {
return errors.New("clair url cannot be empty, pass --clair")
}
const vulnsHelp = `Get a vulnerability report for a repository from a CoreOS Clair server.`
if c.Int("fixable-threshold") < 0 {
return errors.New("fixable threshold must be a positive integer")
}
func (cmd *vulnsCommand) Name() string { return "vulns" }
func (cmd *vulnsCommand) Args() string { return "[OPTIONS] NAME[:TAG|@DIGEST]" }
func (cmd *vulnsCommand) ShortHelp() string { return vulnsHelp }
func (cmd *vulnsCommand) LongHelp() string { return vulnsHelp }
func (cmd *vulnsCommand) Hidden() bool { return false }
if len(c.Args()) < 1 {
return fmt.Errorf("pass the name of the repository")
}
repo, ref, err := repoutils.GetRepoAndRef(c.Args()[0])
if err != nil {
return err
}
// Initialize clair client.
cr, err := clair.New(c.String("clair"), c.GlobalBool("debug"))
if err != nil {
return err
}
// Get the vulnerability report.
report, err := cr.Vulnerabilities(r, repo, ref)
if err != nil {
return err
}
// Iterate over the vulnerabilities by severity list.
for sev, vulns := range report.VulnsBySeverity {
for _, v := range vulns {
if sev == "Fixable" {
fmt.Printf("%s: [%s] \n%s\n%s\n", v.Name, v.Severity+" - Fixable", v.Description, v.Link)
fmt.Printf("Fixed by: %s\n", v.FixedBy)
} else {
fmt.Printf("%s: [%s] \n%s\n%s\n", v.Name, v.Severity, v.Description, v.Link)
}
fmt.Println("-----------------------------------------")
}
}
// Print summary and count.
for sev, vulns := range report.VulnsBySeverity {
fmt.Printf("%s: %d\n", sev, len(vulns))
}
// Return an error if there are more than 1 fixable vulns.
fixable, ok := report.VulnsBySeverity["Fixable"]
if ok {
if len(fixable) > c.Int("fixable-threshold") {
logrus.Fatalf("%d fixable vulnerabilities found", len(fixable))
}
}
// Return an error if there are more than 10 bad vulns.
badVulns := 0
// Include any high vulns.
if highVulns, ok := report.VulnsBySeverity["High"]; ok {
badVulns += len(highVulns)
}
// Include any critical vulns.
if criticalVulns, ok := report.VulnsBySeverity["Critical"]; ok {
badVulns += len(criticalVulns)
}
// Include any defcon1 vulns.
if defcon1Vulns, ok := report.VulnsBySeverity["Defcon1"]; ok {
badVulns += len(defcon1Vulns)
}
if badVulns > 10 {
logrus.Fatalf("%d bad vulnerabilities found", badVulns)
}
return nil
},
func (cmd *vulnsCommand) Register(fs *flag.FlagSet) {
fs.StringVar(&cmd.clairServer, "clair", os.Getenv("CLAIR_URL"), "url to clair instance (or env var CLAIR_URL)")
fs.IntVar(&cmd.fixableThreshold, "fixable-threshhold", 0, "number of fixable issues permitted")
}
type vulnsCommand struct {
clairServer string
fixableThreshold int
}
func (cmd *vulnsCommand) Run(ctx context.Context, args []string) error {
if len(cmd.clairServer) < 1 {
return errors.New("clair url cannot be empty, pass --clair")
}
if cmd.fixableThreshold < 0 {
return errors.New("fixable threshold must be a positive integer")
}
if len(args) < 1 {
return fmt.Errorf("pass the name of the repository")
}
image, err := registry.ParseImage(args[0])
if err != nil {
return err
}
// Create the registry client.
r, err := createRegistryClient(image.Domain)
if err != nil {
return err
}
// Initialize clair client.
cr, err := clair.New(cmd.clairServer, clair.Opt{
Debug: debug,
Timeout: timeout,
Insecure: insecure,
})
if err != nil {
return fmt.Errorf("creation of clair client at %s failed: %v", cmd.clairServer, err)
}
// Get the vulnerability report.
report, err := cr.VulnerabilitiesV3(r, image.Path, image.Reference())
if err != nil {
// Fallback to Clair v2 API.
report, err = cr.Vulnerabilities(r, image.Path, image.Reference())
if err != nil {
return err
}
}
// Iterate over the vulnerabilities by severity list.
for sev, vulns := range report.VulnsBySeverity {
for _, v := range vulns {
if sev == "Fixable" {
fmt.Printf("%s: [%s] \n%s\n%s\n", v.Name, v.Severity+" - Fixable", v.Description, v.Link)
fmt.Printf("Fixed by: %s\n", v.FixedBy)
} else {
fmt.Printf("%s: [%s] \n%s\n%s\n", v.Name, v.Severity, v.Description, v.Link)
}
fmt.Println("-----------------------------------------")
}
}
if len(report.VulnsBySeverity) < 1 {
fmt.Println("No vulnerabilies found.")
return nil
}
// Print summary and count.
for sev, vulns := range report.VulnsBySeverity {
fmt.Printf("%s: %d\n", sev, len(vulns))
}
// Return an error if there are more than 1 fixable vulns.
fixable, ok := report.VulnsBySeverity["Fixable"]
if ok {
if len(fixable) > cmd.fixableThreshold {
logrus.Fatalf("%d fixable vulnerabilities found", len(fixable))
}
}
// Return an error if there are more than 10 bad vulns.
badVulns := 0
// Include any high vulns.
if highVulns, ok := report.VulnsBySeverity["High"]; ok {
badVulns += len(highVulns)
}
// Include any critical vulns.
if criticalVulns, ok := report.VulnsBySeverity["Critical"]; ok {
badVulns += len(criticalVulns)
}
// Include any defcon1 vulns.
if defcon1Vulns, ok := report.VulnsBySeverity["Defcon1"]; ok {
badVulns += len(defcon1Vulns)
}
if badVulns > 10 {
logrus.Fatalf("%d bad vulnerabilities found", badVulns)
}
return nil
}

20
vendor/github.com/genuinetools/reg/vulns_test.go generated vendored Normal file
View file

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