add better generate

Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
Jess Frazelle 2018-03-20 01:33:56 -04:00
parent 3fc6abf56b
commit cdd93563f5
5655 changed files with 1187011 additions and 392 deletions

53
vendor/github.com/genuinetools/reg/.gitignore generated vendored Normal file
View file

@ -0,0 +1,53 @@
###Go###
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
###OSX###
.DS_Store
.AppleDouble
.LSOverride
# Icon must ends with two \r.
Icon
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
reg
server/server
testreg
.certs
cross/
# Go coverage results
coverage.txt
profile.out

62
vendor/github.com/genuinetools/reg/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,62 @@
---
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

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

@ -0,0 +1,32 @@
FROM golang:alpine as builder
MAINTAINER Jessica Frazelle <jess@linux.com>
ENV PATH /go/bin:/usr/local/go/bin:$PATH
ENV GOPATH /go
RUN apk add --no-cache \
ca-certificates
COPY . /go/src/github.com/genuinetools/reg
RUN set -x \
&& apk add --no-cache --virtual .build-deps \
git \
gcc \
libc-dev \
libgcc \
make \
&& cd /go/src/github.com/genuinetools/reg \
&& make static \
&& mv reg /usr/bin/reg \
&& apk del .build-deps \
&& rm -rf /go \
&& echo "Build complete."
FROM scratch
COPY --from=builder /usr/bin/reg /usr/bin/reg
COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs
ENTRYPOINT [ "reg" ]
CMD [ "--help" ]

23
vendor/github.com/genuinetools/reg/Dockerfile.dev generated vendored Normal file
View file

@ -0,0 +1,23 @@
FROM golang:alpine
RUN apk add --no-cache \
build-base \
ca-certificates \
git
ENV DOCKER_BUCKET get.docker.com
ENV DOCKER_VERSION 1.11.1
ENV DOCKER_SHA256 893e3c6e89c0cd2c5f1e51ea41bc2dd97f5e791fcfa3cee28445df277836339d
RUN set -x \
&& apk add --no-cache --virtual .build-deps \
curl \
&& curl -fSL "https://${DOCKER_BUCKET}/builds/Linux/x86_64/docker-$DOCKER_VERSION.tgz" -o docker.tgz \
&& echo "${DOCKER_SHA256} *docker.tgz" | sha256sum -c - \
&& tar -xzvf docker.tgz \
&& mv docker/docker /usr/local/bin/ \
&& rm -rf docker \
&& rm docker.tgz \
&& docker -v \
&& apk del .build-deps

310
vendor/github.com/genuinetools/reg/Gopkg.lock generated vendored Normal file
View file

@ -0,0 +1,310 @@
# 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

65
vendor/github.com/genuinetools/reg/Gopkg.toml generated vendored Normal file
View file

@ -0,0 +1,65 @@
# 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"

21
vendor/github.com/genuinetools/reg/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 The Genuinetools Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

192
vendor/github.com/genuinetools/reg/Makefile generated vendored Normal file
View file

@ -0,0 +1,192 @@
# 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
BUILDTAGS :=
# 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
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: 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
docker run -d \
--name $(DIND_CONTAINER) \
--privileged \
-v $(CURDIR)/.certs:/etc/docker/ssl \
-v $(CURDIR):/go/src/github.com/genuinetools/reg \
-v /tmp:/tmp \
$(DIND_DOCKER_IMAGE) \
dockerd -D --storage-driver $(DOCKER_GRAPHDRIVER) \
-H tcp://127.0.0.1:2375 \
--host=unix:///var/run/docker.sock \
--exec-opt=native.cgroupdriver=cgroupfs \
--insecure-registry localhost:5000 \
--tlsverify \
--tlscacert=/etc/docker/ssl/cacert.pem \
--tlskey=/etc/docker/ssl/server.key \
--tlscert=/etc/docker/ssl/server.cert
.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) .
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) \
-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
.PHONY: help
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

190
vendor/github.com/genuinetools/reg/README.md generated vendored Normal file
View file

@ -0,0 +1,190 @@
# reg
[![Travis CI](https://travis-ci.org/genuinetools/reg.svg?branch=master)](https://travis-ci.org/genuinetools/reg)
Docker registry v2 command line client.
- [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
#### 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)
#### Via Go
```bash
$ go get github.com/genuinetools/reg
```
## Usage
```console
$ reg
NAME:
reg - Docker registry v2 client.
USAGE:
reg [global options] command [command options] [arguments...]
VERSION:
version v0.13.0, build 3b7dafb
AUTHOR:
The Genuinetools Authors <no-reply@butts.com>
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
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
```
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.
## 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
**Repositories**
```console
# this command might take a while if you have hundreds of images like I do
$ reg -r r.j3ss.co ls
Repositories for r.j3ss.co
REPO TAGS
awscli latest
beeswithmachineguns latest
camlistore latest
chrome beta, latest, stable
...
```
**Tags**
```console
$ reg tags tor-browser
alpha
hardened
latest
stable
```
## Get a Manifest
```console
$ reg manifest htop
{
"schemaVersion": 1,
"name": "htop",
"tag": "latest",
"architecture": "amd64",
"fsLayers": [
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
....
],
"history": [
....
]
}
```
## Download a Layer
```console
$ reg layer -o chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
OR
$ reg layer chrome@sha256:a3ed95caeb0.. > layer.tar
```
## Delete an Image
```console
$ reg rm chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
Deleted chrome@sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
```
## Vulnerability Reports
```console
$ reg vulns --clair https://clair.j3ss.co chrome
Found 32 vulnerabilities
CVE-2015-5180: [Low]
https://security-tracker.debian.org/tracker/CVE-2015-5180
-----------------------------------------
CVE-2016-9401: [Low]
popd in bash might allow local users to bypass the restricted shell and cause a use-after-free via a crafted address.
https://security-tracker.debian.org/tracker/CVE-2016-9401
-----------------------------------------
CVE-2016-3189: [Low]
Use-after-free vulnerability in bzip2recover in bzip2 1.0.6 allows remote attackers to cause a denial of service (crash) via a crafted bzip2 file, related to block ends set to before the start of the block.
https://security-tracker.debian.org/tracker/CVE-2016-3189
-----------------------------------------
CVE-2011-3389: [Medium]
The SSL protocol, as used in certain configurations in Microsoft Windows and Microsoft Internet Explorer, Mozilla Firefox, Google Chrome, Opera, and other products, encrypts data by using CBC mode with chained initialization vectors, which allows man-in-the-middle attackers to obtain plaintext HTTP headers via a blockwise chosen-boundary attack (BCBA) on an HTTPS session, in conjunction with JavaScript code that uses (1) the HTML5 WebSocket API, (2) the Java URLConnection API, or (3) the Silverlight WebClient API, aka a "BEAST" attack.
https://security-tracker.debian.org/tracker/CVE-2011-3389
-----------------------------------------
CVE-2016-5318: [Medium]
Stack-based buffer overflow in the _TIFFVGetField function in libtiff 4.0.6 and earlier allows remote attackers to crash the application via a crafted tiff.
https://security-tracker.debian.org/tracker/CVE-2016-5318
-----------------------------------------
CVE-2016-9318: [Medium]
libxml2 2.9.4 and earlier, as used in XMLSec 1.2.23 and earlier and other products, does not offer a flag directly indicating that the current document may be read but other files may not be opened, which makes it easier for remote attackers to conduct XML External Entity (XXE) attacks via a crafted document.
https://security-tracker.debian.org/tracker/CVE-2016-9318
-----------------------------------------
CVE-2015-7554: [High]
The _TIFFVGetField function in tif_dir.c in libtiff 4.0.6 allows attackers to cause a denial of service (invalid memory write and crash) or possibly have unspecified other impact via crafted field data in an extension tag in a TIFF image.
https://security-tracker.debian.org/tracker/CVE-2015-7554
-----------------------------------------
Unknown: 2
Negligible: 23
Low: 3
Medium: 3
High: 1
```
## Testing
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
need to make one modification to your docker daemon config so that you can talk
to the local registry for the tests.
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
to run the tests as docker-in-docker.

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

@ -0,0 +1 @@
v0.13.0

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

@ -0,0 +1,76 @@
package clair
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
// Clair defines the client for retriving information from the clair API.
type Clair struct {
URL string
Client *http.Client
Logf LogfCallback
}
// 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...)
}
// New creates a new Clair struct with the given URL and credentials.
func New(url string, debug bool) (*Clair, error) {
transport := http.DefaultTransport
errorTransport := &ErrorTransport{
Transport: transport,
}
// set the logging
logf := Quiet
if debug {
logf = Log
}
registry := &Clair{
URL: url,
Client: &http.Client{
Timeout: 5 * time.Minute,
Transport: errorTransport,
},
Logf: logf,
}
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
}

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

@ -0,0 +1,78 @@
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
}
resp, err := c.Client.Post(url, "application/json", bytes.NewReader(b))
if err != nil {
return nil, err
}
defer resp.Body.Close()
c.Logf("clair.clair 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)
}

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

@ -0,0 +1,76 @@
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 {
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"`
}

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

@ -0,0 +1,131 @@
package clair
import (
"encoding/base64"
"fmt"
"strings"
"time"
"github.com/docker/distribution/manifest/schema1"
"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),
}
// Get the v1 manifest to pass to clair.
m, err := r.ManifestV1(repo, tag)
if err != nil {
return report, fmt.Errorf("getting the v1 manifest for %s:%s failed: %v", repo, tag, err)
}
// Filter out the empty layers.
var filteredLayers []schema1.FSLayer
for _, layer := range m.FSLayers {
if layer.BlobSum != EmptyLayerBlobSum {
filteredLayers = append(filteredLayers, layer)
}
}
m.FSLayers = filteredLayers
if len(m.FSLayers) == 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(m.FSLayers) - 1; i >= 0; i-- {
// Form the clair layer.
l, err := c.NewClairLayer(r, repo, m.FSLayers, i)
if err != nil {
return report, err
}
// Post the layer.
if _, err := c.PostLayer(l); err != nil {
return report, err
}
}
vl, err := c.GetLayer(m.FSLayers[0].BlobSum.String(), false, 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
}
// NewClairLayer will form a layer struct required for a clar scan
func (c *Clair) NewClairLayer(r *registry.Registry, image string, fsLayers []schema1.FSLayer, index int) (*Layer, error) {
var parentName string
if index < len(fsLayers)-1 {
parentName = fsLayers[index+1].BlobSum.String()
}
// form the path
p := strings.Join([]string{r.URL, "v2", image, "blobs", fsLayers[index].BlobSum.String()}, "/")
useBasicAuth := false
// get the token
token, err := r.Token(p)
if err != nil {
// if we get an error here of type: malformed auth challenge header: 'Basic realm="Registry Realm"'
// we need to use basic auth for the registry
if !strings.Contains(err.Error(), `malformed auth challenge header: 'Basic realm="Registry`) {
return nil, err
}
useBasicAuth = true
}
h := make(map[string]string)
if token != "" && !useBasicAuth {
h = map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", token),
}
}
if token == "" || useBasicAuth {
c.Logf("clair.vulns using basic auth")
h = map[string]string{
"Authorization": fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(r.Username+":"+r.Password))),
}
}
return &Layer{
Name: fsLayers[index].BlobSum.String(),
Path: p,
ParentName: parentName,
Format: "Docker",
Headers: h,
}, nil
}

31
vendor/github.com/genuinetools/reg/delete.go generated vendored Normal file
View file

@ -0,0 +1,31 @@
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
},
}

37
vendor/github.com/genuinetools/reg/delete_test.go generated vendored Normal file
View file

@ -0,0 +1,37 @@
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)
}
}
}

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

@ -0,0 +1,52 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"github.com/genuinetools/reg/repoutils"
digest "github.com/opencontainers/go-digest"
"github.com/urfave/cli"
)
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")
}
repo, ref, err := repoutils.GetRepoAndRef(c.Args()[0])
if err != nil {
return err
}
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
},
}

62
vendor/github.com/genuinetools/reg/list.go generated vendored Normal file
View file

@ -0,0 +1,62 @@
package main
import (
"fmt"
"os"
"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
}
fmt.Printf("Repositories for %s\n", auth.ServerAddress)
// Setup the tab writer.
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
// Print header.
fmt.Fprintln(w, "REPO\tTAGS")
var (
l sync.Mutex
wg sync.WaitGroup
)
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, ", "))
// Lock around the tabwriter to prevent garbled output.
// See: https://github.com/genuinetools/reg/issues/54
l.Lock()
w.Write([]byte(out))
l.Unlock()
wg.Done()
}(repo)
}
wg.Wait()
w.Flush()
return nil
},
}

19
vendor/github.com/genuinetools/reg/list_test.go generated vendored Normal file
View file

@ -0,0 +1,19 @@
package main
import (
"strings"
"testing"
)
func TestList(t *testing.T) {
out, err := run("ls")
if err != nil {
t.Fatalf("output: %s, error: %v", string(out), err)
}
expected := []string{"alpine latest", "busybox glibc, musl"}
for _, e := range expected {
if !strings.Contains(out, e) {
t.Logf("expected to contain: %s\ngot: %s", e, out)
}
}
}

109
vendor/github.com/genuinetools/reg/main.go generated vendored Normal file
View file

@ -0,0 +1,109 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/docker/docker/api/types"
"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
)
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."
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",
},
}
app.Commands = []cli.Command{
deleteCommand,
layerCommand,
listCommand,
manifestCommand,
tagsCommand,
vulnsCommand,
}
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") {
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
}
if err := app.Run(os.Args); err != nil {
logrus.Fatal(err)
}
}

97
vendor/github.com/genuinetools/reg/main_test.go generated vendored Normal file
View file

@ -0,0 +1,97 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"runtime"
"testing"
"github.com/docker/docker/client"
"github.com/genuinetools/reg/testutils"
)
var (
exeSuffix string // ".exe" on Windows
registryConfigs = []struct {
config string
username string
password string
}{
{
config: "noauth.yml",
username: "blah",
password: "blah",
},
{
config: "basicauth.yml",
username: "admin",
password: "testing",
},
}
)
func init() {
switch runtime.GOOS {
case "windows":
exeSuffix = ".exe"
}
}
// The TestMain function creates a reg command for testing purposes and
// deletes it after the tests have been run.
// It also spins up a local registry prefilled with an alpine image and
// removes that after the tests have been run.
func TestMain(m *testing.M) {
// build the test binary
args := []string{"build", "-o", "testreg" + exeSuffix}
out, err := exec.Command("go", args...).CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "building testreg failed: %v\n%s", err, out)
os.Exit(2)
}
// remove test binary
defer os.Remove("testreg" + exeSuffix)
// create the docker client
dcli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
panic(fmt.Errorf("could not connect to docker: %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)
panic(fmt.Errorf("starting registry container %s failed: %v", regConfig.config, err))
}
flag.Parse()
merr := m.Run()
// remove registry
if err := testutils.RemoveContainer(dcli, regID); err != nil {
log.Printf("couldn't remove registry container %s: %v", regConfig.config, err)
}
if merr != 0 {
fmt.Printf("testing config %s failed\n", regConfig.config)
os.Exit(merr)
}
}
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...)
cmd := exec.Command(prog, newargs...)
out, err := cmd.CombinedOutput()
return string(out), err
}

54
vendor/github.com/genuinetools/reg/manifest.go generated vendored Normal file
View file

@ -0,0 +1,54 @@
package main
import (
"encoding/json"
"fmt"
"github.com/genuinetools/reg/repoutils"
"github.com/urfave/cli"
)
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")
}
repo, ref, err := repoutils.GetRepoAndRef(c.Args()[0])
if err != nil {
return err
}
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
},
}

View file

@ -0,0 +1,69 @@
package registry
import (
"fmt"
"net/http"
"net/url"
"regexp"
"strings"
)
var (
bearerRegex = regexp.MustCompile(
`^\s*Bearer\s+(.*)$`)
basicRegex = regexp.MustCompile(`^\s*Basic\s+.*$`)
)
func parseAuthHeader(header http.Header) (*authService, error) {
ch, err := parseChallenge(header.Get("www-authenticate"))
if err != nil {
return nil, err
}
return ch, nil
}
func parseChallenge(challengeHeader string) (*authService, error) {
if basicRegex.MatchString(challengeHeader) {
return nil, nil
}
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]), ",")
var realm, service string
var scope []string
for _, s := range parts {
p := strings.SplitN(s, "=", 2)
if len(p) != 2 {
return nil, fmt.Errorf("malformed auth challenge header: '%s'", challengeHeader)
}
key := p[0]
value := strings.TrimSuffix(strings.TrimPrefix(p[1], `"`), `"`)
switch key {
case "realm":
realm = value
case "service":
service = value
case "scope":
scope = strings.Fields(value)
default:
return nil, fmt.Errorf("unknown field in challege header %s: %v", key, challengeHeader)
}
}
parsedRealm, err := url.Parse(realm)
if err != nil {
return nil, err
}
a := &authService{
Realm: parsedRealm,
Service: service,
Scope: scope,
}
return a, nil
}

View file

@ -0,0 +1,55 @@
package registry
import (
"reflect"
"strings"
"testing"
)
type authServiceMock struct {
service string
realm string
scope []string
}
type challengeTestCase struct {
header string
errorString string
value authServiceMock
}
func (asm authServiceMock) equalTo(v *authService) bool {
if asm.service != v.Service {
return false
}
if reflect.DeepEqual(asm.scope, v.Scope) {
return false
}
if asm.realm != v.Realm.String() {
return false
}
return true
}
func TestParseChallenge(t *testing.T) {
challengeHeaderCases := []challengeTestCase{
{
header: `Bearer realm="https://foobar.com/api/v1/token",service=foobar.com,scope=""`,
value: authServiceMock{
service: "foobar.com",
realm: "https://foobar.com/api/v1/token",
},
},
}
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 !tc.value.equalTo(val) {
t.Fatalf("got %v, expected %v", val, tc.value)
}
}
}

View file

@ -0,0 +1,25 @@
package registry
import (
"net/http"
"strings"
)
// BasicTransport defines the data structure for authentication via basic auth.
type BasicTransport struct {
Transport http.RoundTripper
URL string
Username string
Password string
}
// 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 t.Username != "" || t.Password != "" {
req.SetBasicAuth(t.Username, t.Password)
}
}
resp, err := t.Transport.RoundTrip(req)
return resp, err
}

39
vendor/github.com/genuinetools/reg/registry/catalog.go generated vendored Normal file
View file

@ -0,0 +1,39 @@
package registry
import (
"net/url"
"github.com/peterhellberg/link"
)
type catalogResponse struct {
Repositories []string `json:"repositories"`
}
// Catalog returns the repositories in a registry.
func (r *Registry) Catalog(u string) ([]string, error) {
if u == "" {
u = "/v2/_catalog"
}
uri := r.url(u)
r.Logf("registry.catalog url=%s", uri)
var response catalogResponse
h, err := r.getJSON(uri, &response, false)
if err != nil {
return nil, err
}
for _, l := range link.ParseHeader(h) {
if l.Rel == "next" {
unescaped, _ := url.QueryUnescape(l.URI)
repos, err := r.Catalog(unescaped)
if err != nil {
return nil, err
}
response.Repositories = append(response.Repositories, repos...)
}
}
return response.Repositories, nil
}

47
vendor/github.com/genuinetools/reg/registry/delete.go generated vendored Normal file
View file

@ -0,0 +1,47 @@
package registry
import (
"fmt"
"net/http"
"github.com/docker/distribution/manifest/schema2"
ocd "github.com/opencontainers/go-digest"
)
// Delete removes a repository digest or reference 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.
url := r.url("/v2/%s/manifests/%s", repository, digest)
r.Logf("registry.manifests.delete url=%s repository=%s digest=%s",
url, repository, digest)
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return err
}
req.Header.Set("Accept", schema2.MediaTypeManifest)
resp, err := r.Client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusNotFound {
return nil
}
return fmt.Errorf("Got status code: %d", resp.StatusCode)
}

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

@ -0,0 +1,34 @@
package registry
import (
"fmt"
"net/http"
"github.com/docker/distribution/manifest/schema2"
)
// 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)
r.Logf("registry.manifests.get url=%s repository=%s ref=%s",
url, repository, ref)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("Accept", schema2.MediaTypeManifest)
resp, err := r.Client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotFound {
return "", fmt.Errorf("Got status code: %d", resp.StatusCode)
}
digest := resp.Header.Get("Docker-Content-Digest")
return digest, nil
}

View file

@ -0,0 +1,46 @@
package registry
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 || resp.StatusCode == http.StatusUnauthorized {
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
}

89
vendor/github.com/genuinetools/reg/registry/layer.go generated vendored Normal file
View file

@ -0,0 +1,89 @@
package registry
import (
"io"
"net/http"
"net/url"
"github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest"
)
// DownloadLayer downloads a specific layer by digest for a repository.
func (r *Registry) DownloadLayer(repository string, digest digest.Digest) (io.ReadCloser, error) {
url := r.url("/v2/%s/blobs/%s", repository, digest)
r.Logf("registry.layer.download url=%s repository=%s digest=%s", url, repository, digest)
resp, err := r.Client.Get(url)
if err != nil {
return nil, err
}
return resp.Body, nil
}
// 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)
if err != nil {
return err
}
q := uploadURL.Query()
q.Set("digest", digest.String())
uploadURL.RawQuery = q.Encode()
r.Logf("registry.layer.upload url=%s repository=%s digest=%s", uploadURL, repository, digest)
upload, err := http.NewRequest("PUT", uploadURL.String(), content)
if err != nil {
return err
}
upload.Header.Set("Content-Type", "application/octet-stream")
_, err = r.Client.Do(upload)
return err
}
// HasLayer returns if the registry contains the specific digest for a repository.
func (r *Registry) HasLayer(repository string, digest digest.Digest) (bool, error) {
checkURL := r.url("/v2/%s/blobs/%s", repository, digest)
r.Logf("registry.layer.check url=%s repository=%s digest=%s", checkURL, repository, digest)
resp, err := r.Client.Head(checkURL)
if err == nil {
defer resp.Body.Close()
return resp.StatusCode == http.StatusOK, nil
}
urlErr, ok := err.(*url.Error)
if !ok {
return false, err
}
httpErr, ok := urlErr.Err.(*httpStatusError)
if !ok {
return false, err
}
if httpErr.Response.StatusCode == http.StatusNotFound {
return false, nil
}
return false, err
}
func (r *Registry) initiateUpload(repository string) (*url.URL, 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
}
defer resp.Body.Close()
location := resp.Header.Get("Location")
locationURL, err := url.Parse(location)
if err != nil {
return nil, err
}
return locationURL, nil
}

View file

@ -0,0 +1,86 @@
package registry
import (
"io/ioutil"
"net/http"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
)
// Manifest returns the manifest for a specific repository:tag.
func (r *Registry) Manifest(repository, ref string) (distribution.Manifest, error) {
uri := r.url("/v2/%s/manifests/%s", repository, ref)
r.Logf("registry.manifests uri=%s repository=%s ref=%s", uri, repository, ref)
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, err
}
req.Header.Add("Accept", schema2.MediaTypeManifest)
req.Header.Add("Accept", manifestlist.MediaTypeManifestList)
resp, err := r.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
r.Logf("registry.manifests resp.Status=%s, body=%s", resp.Status, body)
m, _, err := distribution.UnmarshalManifest(resp.Header.Get("Content-Type"), body)
if err != nil {
return nil, err
}
return m, nil
}
// ManifestList gets the registry v2 manifest list.
func (r *Registry) ManifestList(repository, ref string) (manifestlist.ManifestList, error) {
uri := r.url("/v2/%s/manifests/%s", repository, ref)
r.Logf("registry.manifests uri=%s repository=%s ref=%s", uri, repository, ref)
var m manifestlist.ManifestList
if _, err := r.getJSON(uri, &m, true); err != nil {
r.Logf("registry.manifests response=%v", m)
return m, err
}
return m, nil
}
// ManifestV2 gets the registry v2 manifest.
func (r *Registry) ManifestV2(repository, ref string) (schema2.Manifest, error) {
uri := r.url("/v2/%s/manifests/%s", repository, ref)
r.Logf("registry.manifests uri=%s repository=%s ref=%s", uri, repository, ref)
var m schema2.Manifest
if _, err := r.getJSON(uri, &m, true); err != nil {
r.Logf("registry.manifests response=%v", m)
return m, err
}
return m, nil
}
// ManifestV1 gets the registry v1 manifest.
func (r *Registry) ManifestV1(repository, ref string) (schema1.SignedManifest, error) {
uri := r.url("/v2/%s/manifests/%s", repository, ref)
r.Logf("registry.manifests uri=%s repository=%s ref=%s", uri, repository, ref)
var m schema1.SignedManifest
if _, err := r.getJSON(uri, &m, false); err != nil {
r.Logf("registry.manifests response=%v", m)
return m, err
}
return m, nil
}

12
vendor/github.com/genuinetools/reg/registry/ping.go generated vendored Normal file
View file

@ -0,0 +1,12 @@
package registry
// Ping tries to contact a registry URL to make sure it is up and accessible.
func (r *Registry) Ping() error {
url := r.url("/v2/")
r.Logf("registry.ping url=%s", url)
resp, err := r.Client.Get(url)
if resp != nil {
defer resp.Body.Close()
}
return err
}

133
vendor/github.com/genuinetools/reg/registry/registry.go generated vendored Normal file
View file

@ -0,0 +1,133 @@
package registry
import (
"crypto/tls"
"encoding/json"
"fmt"
"log"
"net/http"
"regexp"
"strings"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema2"
"github.com/docker/docker/api/types"
)
// Registry defines the client for retriving information from the registry API.
type Registry struct {
URL string
Domain string
Username string
Password string
Client *http.Client
Logf LogfCallback
}
var reProtocol = regexp.MustCompile("^https?://")
// 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...)
}
// New creates a new Registry struct with the given URL and credentials.
func New(auth types.AuthConfig, debug bool) (*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,
},
}
return newFromTransport(auth, transport, debug)
}
func newFromTransport(auth types.AuthConfig, transport http.RoundTripper, debug bool) (*Registry, error) {
url := strings.TrimSuffix(auth.ServerAddress, "/")
if !reProtocol.MatchString(url) {
url = "https://" + url
}
tokenTransport := &TokenTransport{
Transport: transport,
Username: auth.Username,
Password: auth.Password,
}
basicAuthTransport := &BasicTransport{
Transport: tokenTransport,
URL: url,
Username: auth.Username,
Password: auth.Password,
}
errorTransport := &ErrorTransport{
Transport: basicAuthTransport,
}
// set the logging
logf := Quiet
if debug {
logf = Log
}
registry := &Registry{
URL: url,
Domain: reProtocol.ReplaceAllString(url, ""),
Client: &http.Client{
Transport: errorTransport,
},
Username: auth.Username,
Password: auth.Password,
Logf: logf,
}
if err := registry.Ping(); err != nil {
return nil, err
}
return registry, nil
}
// url returns a registry URL with the passed arguements concatenated.
func (r *Registry) url(pathTemplate string, args ...interface{}) string {
pathSuffix := fmt.Sprintf(pathTemplate, args...)
url := fmt.Sprintf("%s%s", r.URL, pathSuffix)
return url
}
func (r *Registry) getJSON(url string, response interface{}, addV2Header bool) (http.Header, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
if addV2Header {
req.Header.Add("Accept", schema2.MediaTypeManifest)
req.Header.Add("Accept", manifestlist.MediaTypeManifestList)
}
resp, err := r.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
r.Logf("registry.registry resp.Status=%s", resp.Status)
if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
return nil, err
}
return resp.Header, nil
}

18
vendor/github.com/genuinetools/reg/registry/tags.go generated vendored Normal file
View file

@ -0,0 +1,18 @@
package registry
type tagsResponse struct {
Tags []string `json:"tags"`
}
// Tags returns the tags for a specific repository.
func (r *Registry) Tags(repository string) ([]string, error) {
url := r.url("/v2/%s/tags/list", repository)
r.Logf("registry.tags url=%s repository=%s", url, repository)
var response tagsResponse
if _, err := r.getJSON(url, &response, false); err != nil {
return nil, err
}
return response.Tags, nil
}

View file

@ -0,0 +1,166 @@
package registry
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
)
// TokenTransport defines the data structure for authentication via tokens.
type TokenTransport struct {
Transport http.RoundTripper
Username string
Password string
}
// RoundTrip defines the round tripper for token transport.
func (t *TokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := t.Transport.RoundTrip(req)
if err != nil {
return resp, err
}
authService, err := isTokenDemand(resp)
if err != nil {
return nil, err
}
if authService == nil {
return resp, nil
}
return t.authAndRetry(authService, req)
}
type authToken struct {
Token string `json:"token"`
}
func (t *TokenTransport) authAndRetry(authService *authService, req *http.Request) (*http.Response, error) {
token, authResp, err := t.auth(authService)
if err != nil {
return authResp, err
}
return t.retry(req, token)
}
func (t *TokenTransport) auth(authService *authService) (string, *http.Response, error) {
authReq, err := authService.Request(t.Username, t.Password)
if err != nil {
return "", nil, err
}
c := http.Client{
Transport: t.Transport,
}
resp, err := c.Do(authReq)
if err != nil {
return "", nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", resp, err
}
var authToken authToken
if err := json.NewDecoder(resp.Body).Decode(&authToken); err != nil {
return "", nil, err
}
return authToken.Token, nil, nil
}
func (t *TokenTransport) retry(req *http.Request, token string) (*http.Response, error) {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
return t.Transport.RoundTrip(req)
}
type authService struct {
Realm *url.URL
Service string
Scope []string
}
func (a *authService) Request(username, password string) (*http.Request, error) {
q := a.Realm.Query()
q.Set("service", a.Service)
for _, s := range a.Scope {
q.Set("scope", s)
}
// q.Set("scope", "repository:r.j3ss.co/htop:push,pull")
a.Realm.RawQuery = q.Encode()
req, err := http.NewRequest("GET", a.Realm.String(), nil)
if username != "" || password != "" {
req.SetBasicAuth(username, password)
}
return req, err
}
func isTokenDemand(resp *http.Response) (*authService, error) {
if resp == nil {
return nil, nil
}
if resp.StatusCode != http.StatusUnauthorized {
return nil, nil
}
return parseAuthHeader(resp.Header)
}
// Token returns the required token for the specific resource url.
func (r *Registry) Token(url string) (string, error) {
r.Logf("registry.token url=%s", url)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
resp, err := r.Client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
a, err := isTokenDemand(resp)
if err != nil {
return "", err
}
if a == nil {
r.Logf("registry.token authService=nil")
return "", nil
}
authReq, err := a.Request(r.Username, r.Password)
if err != nil {
return "", err
}
resp, err = r.Client.Do(authReq)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("Getting token failed with StatusCode != StatusOK but %d", resp.StatusCode)
}
var authToken authToken
if err := json.NewDecoder(resp.Body).Decode(&authToken); err != nil {
return "", err
}
if authToken.Token == "" {
return "", errors.New("Auth token cannot be empty")
}
return authToken.Token, nil
}

View file

@ -0,0 +1,141 @@
package repoutils
import (
"fmt"
"strings"
"github.com/docker/distribution/reference"
"github.com/docker/docker-ce/components/cli/cli/config"
"github.com/docker/docker/api/types"
)
const (
// DefaultDockerRegistry is the default docker registry address.
DefaultDockerRegistry = "https://registry-1.docker.io"
latestTagSuffix = ":latest"
)
// GetAuthConfig returns the docker registry AuthConfig.
// Optionally takes in the authentication values, otherwise pulls them from the
// docker config file.
func GetAuthConfig(username, password, registry string) (types.AuthConfig, error) {
if username != "" && password != "" && registry != "" {
return types.AuthConfig{
Username: username,
Password: password,
ServerAddress: registry,
}, nil
}
dcfg, err := config.Load(config.Dir())
if err != nil {
return types.AuthConfig{}, fmt.Errorf("Loading config file failed: %v", err)
}
// return error early if there are no auths saved
if !dcfg.ContainsAuth() {
// If we were passed a registry, just use that.
if registry != "" {
return setDefaultRegistry(types.AuthConfig{
ServerAddress: registry,
}), nil
}
// Otherwise, just use an empty auth config.
return types.AuthConfig{}, nil
}
authConfigs, err := dcfg.GetAllCredentials()
if err != nil {
return types.AuthConfig{}, fmt.Errorf("Getting credentials failed: %v", err)
}
// if they passed a specific registry, return those creds _if_ they exist
if registry != "" {
// try with the user input
if creds, ok := authConfigs[registry]; ok {
return creds, nil
}
// remove https:// from user input and try again
if strings.HasPrefix(registry, "https://") {
if creds, ok := authConfigs[strings.TrimPrefix(registry, "https://")]; ok {
return creds, nil
}
}
// remove http:// from user input and try again
if strings.HasPrefix(registry, "http://") {
if creds, ok := authConfigs[strings.TrimPrefix(registry, "http://")]; ok {
return creds, nil
}
}
// add https:// to user input and try again
// see https://github.com/genuinetools/reg/issues/32
if !strings.HasPrefix(registry, "https://") && !strings.HasPrefix(registry, "http://") {
if creds, ok := authConfigs["https://"+registry]; ok {
return creds, nil
}
}
fmt.Printf("Using registry %q with no authentication\n", registry)
// Otherwise just use the registry with no auth.
return setDefaultRegistry(types.AuthConfig{
ServerAddress: registry,
}), nil
}
// Just set the auth config as the first registryURL, username and password
// found in the auth config.
for _, creds := range authConfigs {
fmt.Printf("No registry passed. Using registry %q\n", creds.ServerAddress)
return creds, nil
}
// Don't use any authentication.
// We should never get here.
fmt.Println("Not using any authentication")
return types.AuthConfig{}, nil
}
// GetRepoAndRef parses the repo name and reference.
func GetRepoAndRef(image string) (repo, ref string, err error) {
if image == "" {
return "", "", reference.ErrNameEmpty
}
image = addLatestTagSuffix(image)
var parts []string
if strings.Contains(image, "@") {
parts = strings.Split(image, "@")
} else if strings.Contains(image, ":") {
parts = strings.Split(image, ":")
}
repo = parts[0]
if len(parts) > 1 {
ref = parts[1]
}
return
}
// addLatestTagSuffix adds :latest to the image if it does not have a tag
func addLatestTagSuffix(image string) string {
if !strings.Contains(image, ":") {
return image + latestTagSuffix
}
return image
}
func setDefaultRegistry(auth types.AuthConfig) types.AuthConfig {
if auth.ServerAddress == "docker.io" {
auth.ServerAddress = DefaultDockerRegistry
}
return auth
}

View file

@ -0,0 +1,200 @@
package repoutils
import (
"errors"
"os"
"path/filepath"
"strings"
"testing"
"github.com/docker/distribution/reference"
"github.com/docker/docker-ce/components/cli/cli/config"
"github.com/docker/docker/api/types"
"github.com/google/go-cmp/cmp"
)
func TestGetAuthConfig(t *testing.T) {
configTestcases := []struct {
name string
username, password, registry string
configdir string
err error
config types.AuthConfig
}{
{
name: "pass in all details",
username: "jess",
password: "password",
registry: "r.j3ss.co",
config: types.AuthConfig{
Username: "jess",
Password: "password",
ServerAddress: "r.j3ss.co",
},
},
{
name: "invalid config dir",
configdir: "testdata/invalid",
err: errors.New("Loading config file failed: "),
config: types.AuthConfig{},
},
{
name: "empty config",
configdir: "testdata/empty",
config: types.AuthConfig{},
},
{
name: "empty config with docker.io",
registry: "docker.io",
configdir: "testdata/empty",
config: types.AuthConfig{
ServerAddress: DefaultDockerRegistry,
},
},
{
name: "empty config with registry",
registry: "r.j3ss.co",
configdir: "testdata/empty",
config: types.AuthConfig{
ServerAddress: "r.j3ss.co",
},
},
{
name: "valid with multiple",
registry: "r.j3ss.co",
configdir: "testdata/valid",
config: types.AuthConfig{
ServerAddress: "r.j3ss.co",
Username: "user",
Password: "blah\n",
},
},
{
name: "valid with multiple and https:// prefix",
registry: "https://r.j3ss.co",
configdir: "testdata/valid",
config: types.AuthConfig{
ServerAddress: "r.j3ss.co",
Username: "user",
Password: "blah\n",
},
},
{
name: "valid with multiple and http:// prefix",
registry: "http://r.j3ss.co",
configdir: "testdata/valid",
config: types.AuthConfig{
ServerAddress: "r.j3ss.co",
Username: "user",
Password: "blah\n",
},
},
{
name: "valid with multiple and no https:// prefix",
registry: "reg.j3ss.co",
configdir: "testdata/valid",
config: types.AuthConfig{
ServerAddress: "https://reg.j3ss.co",
Username: "joe",
Password: "otherthing\n",
},
},
{
name: "valid with multiple and but registry not found",
registry: "otherreg.j3ss.co",
configdir: "testdata/valid",
config: types.AuthConfig{
ServerAddress: "otherreg.j3ss.co",
},
},
{
name: "valid and no registry passed",
configdir: "testdata/singlevalid",
config: types.AuthConfig{
ServerAddress: "https://index.docker.io/v1/",
Username: "user",
Password: "thing\n",
},
},
}
for _, testcase := range configTestcases {
if testcase.configdir != "" {
// Set the config directory.
wd, err := os.Getwd()
if err != nil {
t.Fatalf("get working directory failed: %v", err)
}
config.SetDir(filepath.Join(wd, testcase.configdir))
}
cfg, err := GetAuthConfig(testcase.username, testcase.password, testcase.registry)
if err != nil || testcase.err != nil {
if err == nil || testcase.err == nil {
t.Fatalf("%q: expected err (%v), got err (%v)", testcase.name, testcase.err, err)
}
if !strings.Contains(err.Error(), testcase.err.Error()) {
t.Fatalf("%q: expected err (%v), got err (%v)", testcase.name, testcase.err, err)
}
continue
}
if diff := cmp.Diff(testcase.config, cfg); diff != "" {
t.Errorf("%s: authconfig differs: (-got +want)\n%s", testcase.name, diff)
}
}
}
func TestGetRepoAndRef(t *testing.T) {
imageTestcases := []struct {
// input is the repository name or name component testcase
input string
// err is the error expected from Parse, or nil
err error
// repository is the string representation for the reference
repository string
// ref the reference
ref string
}{
{
input: "alpine",
repository: "alpine",
ref: "latest",
},
{
input: "docker:dind",
repository: "docker",
ref: "dind",
},
{
input: "",
err: reference.ErrNameEmpty,
},
{
input: "chrome@sha256:2a6c8ad38c41ae5122d76be59b34893d7fa1bdfaddd85bf0e57d0d16c0f7f91e",
repository: "chrome",
ref: "sha256:2a6c8ad38c41ae5122d76be59b34893d7fa1bdfaddd85bf0e57d0d16c0f7f91e",
},
}
for _, testcase := range imageTestcases {
repo, ref, err := GetRepoAndRef(testcase.input)
if err != nil || testcase.err != nil {
if err == nil || testcase.err == nil {
t.Fatalf("%q: expected err (%v), got err (%v)", testcase.input, testcase.err, err)
}
if err.Error() != testcase.err.Error() {
t.Fatalf("%q: expected err (%v), got err (%v)", testcase.input, testcase.err, err)
}
continue
}
if testcase.repository != repo {
t.Fatalf("%q: expected repo (%s), got repo (%s)", testcase.input, testcase.repository, repo)
}
if testcase.ref != ref {
t.Fatalf("%q: expected ref (%s), got ref (%s)", testcase.input, testcase.ref, ref)
}
}
}

34
vendor/github.com/genuinetools/reg/server/Dockerfile generated vendored Normal file
View file

@ -0,0 +1,34 @@
FROM golang:alpine as builder
MAINTAINER Jessica Frazelle <jess@linux.com>
ENV PATH /go/bin:/usr/local/go/bin:$PATH
ENV GOPATH /go
RUN apk add --no-cache \
ca-certificates
RUN set -x \
&& apk add --no-cache --virtual .build-deps \
git \
gcc \
libc-dev \
libgcc \
&& go get -v github.com/genuinetools/reg \
&& cd /go/src/github.com/genuinetools/reg \
&& CGO_ENABLED=0 go build -a -tags netgo -ldflags '-extldflags "-static"' -o /usr/bin/reg-server ./server \
&& apk del .build-deps \
&& rm -rf /go \
&& echo "Build complete."
FROM scratch
COPY --from=builder /usr/bin/reg-server /usr/bin/reg-server
COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs
COPY static /src/static
COPY templates /src/templates
WORKDIR /src
ENTRYPOINT [ "reg-server" ]
CMD [ "--help" ]

46
vendor/github.com/genuinetools/reg/server/README.md generated vendored Normal file
View file

@ -0,0 +1,46 @@
# reg-server
A static UI for a docker registry. Comes with vulnerability scanning if you
have a [CoreOS Clair](https://github.com/coreos/clair) server set up.
Demo at [r.j3ss.co](https://r.j3ss.co).
## Usage
```console
$ reg-server -h
NAME:
reg-server - Docker registry v2 static UI server.
USAGE:
reg-server [global options] command [command options] [arguments...]
VERSION:
v0.1.0
AUTHOR:
The Genuinetools Authors <no-reply@butts.com>
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--debug, -d run in debug mode
--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)
--insecure, -k do not verify tls certificates of registry
--port value port for server to run on (default: "8080")
--cert value path to ssl cert
--key value path to ssl key
--interval value interval to generate new index.html's at (default: "1h")
--clair value url to clair instance
--help, -h show help
--version, -v print the version
```
## Screenshots
![home.png](home.png)
![vuln.png](vuln.png)

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

@ -0,0 +1,257 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"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
}
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"`
}
func (rc *registryController) repositories(staticDir string) error {
updating = true
logrus.Info("fetching catalog")
result := AnalysisResult{
RegistryDomain: rc.reg.Domain,
LastUpdated: time.Now().Local().Format(time.RFC1123),
}
repoList, err := r.Catalog("")
if err != nil {
return fmt.Errorf("getting catalog failed: %v", err)
}
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)
}
// parse & execute the template
logrus.Info("executing the template repositories")
path := filepath.Join(staticDir, "index.html")
if err := os.MkdirAll(filepath.Dir(path), 0644); err != nil {
return err
}
logrus.Debugf("creating/opening file %s", path)
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
if err := tmpl.ExecuteTemplate(f, "repositories", result); err != nil {
f.Close()
return fmt.Errorf("execute template repositories failed: %v", err)
}
updating = false
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")
vars := mux.Vars(r)
repo, err := url.QueryUnescape(vars["repo"])
if err != nil || repo == "" {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Empty repo")
return
}
tags, err := rc.reg.Tags(repo)
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.StatusNotFound)
fmt.Fprint(w, "No tags found")
return
}
// 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 {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "No tags found")
return
}
result := AnalysisResult{
RegistryDomain: rc.reg.Domain,
LastUpdated: time.Now().Local().Format(time.RFC1123),
Name: repo,
}
for _, tag := range tags {
// get the manifest
m1, err := rc.reg.ManifestV1(repo, tag)
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "tags",
"URL": r.URL,
"method": r.Method,
"repo": repo,
"tag": tag,
}).Errorf("getting v1 manifest for %s:%s failed: %v", repo, tag, err)
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Manifest not found")
return
}
var createdDate time.Time
for _, h := range m1.History {
var comp v1Compatibility
if err := json.Unmarshal([]byte(h.V1Compatibility), &comp); err != nil {
logrus.WithFields(logrus.Fields{
"func": "tags",
"URL": r.URL,
"method": r.Method,
}).Errorf("unmarshal v1 manifest for %s:%s failed: %v", repo, tag, err)
w.WriteHeader(http.StatusInternalServerError)
return
}
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)
}
if err := tmpl.ExecuteTemplate(w, "tags", result); err != nil {
logrus.WithFields(logrus.Fields{
"func": "tags",
"URL": r.URL,
"method": r.Method,
}).Errorf("template rendering failed: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
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")
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
}
result := clair.VulnerabilityReport{}
if rc.cl != nil {
result, err = rc.cl.Vulnerabilities(rc.reg, repo, tag)
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
}
if err := 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
}
}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

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

@ -0,0 +1,248 @@
package main
import (
"html/template"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/genuinetools/reg/clair"
"github.com/genuinetools/reg/registry"
"github.com/genuinetools/reg/repoutils"
"github.com/gorilla/mux"
wordwrap "github.com/mitchellh/go-wordwrap"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
const (
// VERSION is the binary version.
VERSION = "v0.2.0"
)
var (
updating = false
r *registry.Registry
cl *clair.Clair
tmpl *template.Template
)
// preload initializes any global options and configuration
// before the main or sub commands are run.
func preload(c *cli.Context) (err error) {
if c.GlobalBool("debug") {
logrus.SetLevel(logrus.DebugLevel)
}
return nil
}
func main() {
app := cli.NewApp()
app.Name = "reg-server"
app.Version = VERSION
app.Author = "The Genuinetools Authors"
app.Email = "no-reply@butts.com"
app.Usage = "Docker registry v2 static UI server."
app.Before = preload
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "debug, d",
Usage: "run in debug mode",
},
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)",
},
cli.BoolFlag{
Name: "insecure, k",
Usage: "do not verify tls certificates of registry",
},
cli.BoolFlag{
Name: "once, o",
Usage: "generate an output once and then exit",
},
cli.StringFlag{
Name: "port",
Value: "8080",
Usage: "port for server to run on",
},
cli.StringFlag{
Name: "cert",
Usage: "path to ssl cert",
},
cli.StringFlag{
Name: "key",
Usage: "path to ssl key",
},
cli.StringFlag{
Name: "interval",
Value: "1h",
Usage: "interval to generate new index.html's at",
},
cli.StringFlag{
Name: "clair",
Usage: "url to clair instance",
},
}
app.Action = func(c *cli.Context) error {
auth, err := repoutils.GetAuthConfig(c.GlobalString("username"), c.GlobalString("password"), c.GlobalString("registry"))
if err != nil {
logrus.Fatal(err)
}
// create the registry client
if c.GlobalBool("insecure") {
r, err = registry.NewInsecure(auth, c.GlobalBool("debug"))
if err != nil {
logrus.Fatal(err)
}
} else {
r, err = registry.New(auth, c.GlobalBool("debug"))
if err != nil {
logrus.Fatal(err)
}
}
// create a clair instance if needed
if c.GlobalString("clair") != "" {
cl, err = clair.New(c.GlobalString("clair"), c.GlobalBool("debug"))
if err != nil {
logrus.Warnf("creation of clair failed: %v", err)
}
}
// get the path to the static directory
wd, err := os.Getwd()
if err != nil {
logrus.Fatal(err)
}
staticDir := filepath.Join(wd, "static")
// create the template
templateDir := filepath.Join(staticDir, "../templates")
// make sure all the templates exist
vulns := filepath.Join(templateDir, "vulns.html")
if _, err := os.Stat(vulns); os.IsNotExist(err) {
logrus.Fatalf("Template %s not found", vulns)
}
layout := filepath.Join(templateDir, "repositories.html")
if _, err := os.Stat(layout); os.IsNotExist(err) {
logrus.Fatalf("Template %s not found", layout)
}
tags := filepath.Join(templateDir, "tags.html")
if _, err := os.Stat(tags); os.IsNotExist(err) {
logrus.Fatalf("Template %s not found", tags)
}
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"
}
},
}
tmpl = template.Must(template.New("").Funcs(funcMap).ParseGlob(templateDir + "/*.html"))
rc := registryController{
reg: r,
cl: cl,
}
// create the initial index
logrus.Info("creating initial static index")
if err := rc.repositories(staticDir); err != nil {
logrus.Fatalf("Error creating index: %v", err)
}
if c.GlobalBool("once") {
logrus.Info("Output generated")
return nil
}
// parse the duration
dur, err := time.ParseDuration(c.String("interval"))
if err != nil {
logrus.Fatalf("parsing %s as duration failed: %v", c.String("interval"), err)
}
ticker := time.NewTicker(dur)
go func() {
// create more indexes every X minutes based off interval
for range ticker.C {
if !updating {
logrus.Info("creating timer based static index")
if err := rc.repositories(staticDir); err != nil {
logrus.Warnf("creating static index failed: %v", err)
updating = false
}
} else {
logrus.Warnf("skipping timer based static index update for %s", c.String("interval"))
}
}
}()
// create mux server
mux := mux.NewRouter()
mux.UseEncodedPath()
// static files handler
staticHandler := http.FileServer(http.Dir(staticDir))
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)
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)
mux.PathPrefix("/static/").Handler(http.StripPrefix("/static/", staticHandler))
mux.Handle("/", staticHandler)
// set up the server
port := c.String("port")
server := &http.Server{
Addr: ":" + port,
Handler: mux,
}
logrus.Infof("Starting server on port %q", port)
if c.String("cert") != "" && c.String("key") != "" {
logrus.Fatal(server.ListenAndServeTLS(c.String("cert"), c.String("key")))
} else {
logrus.Fatal(server.ListenAndServe())
}
return nil
}
app.Run(os.Args)
}

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,60 @@
{{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">
<a href="https://twitter.com/jessfraz">@jessfraz</a>
<p>Last Updated: {{ .LastUpdated }}</p>
</div><!--/.footer-->
<script src="/static/js/scripts.js"></script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-29404280-12', 'jessfraz.com');
ga('send', 'pageview');
</script>
</body>
</html>
{{end}}

View file

@ -0,0 +1,77 @@
{{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>
<th>Vulnerabilities</th>
</tr>
{{ range $key, $value := .Repositories }}
<tr>
<td valign="left" nowrap>
<a href="/repo/{{ $value.Name | urlquery }}/tag/{{ $value.Tag }}/vulns">
{{ $value.Name }}
</a>
</td>
<td align="right" nowrap>
<a href="/repo/{{ $value.Name | urlquery }}/tag/{{ $value.Tag }}/vulns">
{{ $value.Tag }}
</a>
</td>
<td align="right" nowrap>
{{ $value.Created.Format "02 Jan, 2006 15:04:05 UTC" }}
</td>
<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>
</tr>
{{ end }}
</table>
</div>
<div class="footer">
<a href="https://twitter.com/jessfraz">@jessfraz</a>
<p>Last Updated: {{ .LastUpdated }}</p>
</div><!--/.footer-->
<script src="/static/js/scripts.js"></script>
<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>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-29404280-12', 'jessfraz.com');
ga('send', 'pageview');
</script>
</body>
</html>
{{end}}

View file

@ -0,0 +1,78 @@
{{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}}
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-29404280-12', 'jessfraz.com');
ga('send', 'pageview');
</script>
</body>
</html>
{{end}}

View file

@ -0,0 +1,15 @@
{{define "vulns"}}CVE Report for {{.Repo}}:{{.Tag}}
Generated on: {{.Date}}
Vulnerabilities Found: {{len .Vulns}}
{{range $key, $value := .VulnsBySeverity}}{{$key}}: {{len $value}}
{{end}}
{{if gt .BadVulns 5}}------------------------------------ ALERT ------------------------------------
{{.BadVulns}} High, Critical, and/or Defcon1 vulnerabilities found
{{end}}{{range $vulns := .VulnsBySeverity}}{{range $value := $vulns}}{{$value.Name}}: [{{$value.Severity}}]
{{trim $value.Description}}
{{$value.Link}}
-------------------------------------------------------------------------------
{{end}}{{end}}{{end}}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

28
vendor/github.com/genuinetools/reg/tags.go generated vendored Normal file
View file

@ -0,0 +1,28 @@
package main
import (
"fmt"
"strings"
"github.com/urfave/cli"
)
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")
}
tags, err := r.Tags(c.Args()[0])
if err != nil {
return err
}
// Print the tags.
fmt.Println(strings.Join(tags, "\n"))
return nil
},
}

19
vendor/github.com/genuinetools/reg/tags_test.go generated vendored Normal file
View file

@ -0,0 +1,19 @@
package main
import (
"strings"
"testing"
)
func TestTags(t *testing.T) {
out, err := run("tags", "busybox")
if err != nil {
t.Fatalf("output: %s, error: %v", string(out), err)
}
expected := `glibc
musl
`
if !strings.HasSuffix(out, expected) {
t.Logf("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 @@
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,19 @@
-----BEGIN CERTIFICATE-----
MIIDAjCCAeqgAwIBAgIQC+Tw335jnu9Z46unSVFfeTANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMB4XDTE3MDYwNjAwMDAyNFoXDTE4MDYwNjAwMDAy
NFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAN8/Xqef2iozUMXLRCvHnGc+SMaEDuW/iM9n/IL68F11esAR6tXhaiRQ
RdEsM9MyfyDYt9juE+XLaMyqhTAXwK9YzULE4BTVbAr9uOgxLtyWspA4uWfxhcrq
CqQTRc0U95ZEnAVSjytDAXtLQyP+UlnMzmMhDpzRuH1YXqm6qB07G5yOJyPKkDrq
EyqUsjqBJmBkUJiYfSx95Jen+4ZzlSR7wOsoxei09QYyvo3DMfcJO8Wb8IQFjOT0
ohhBFBR3v1HOOT6bKuhUHif3K+STguMEhHrgAFmcFW3NPQ3It2SyKfGBZ8nbmSA9
2tjojQEFUHqRKp0UWyObuUmNAo1U1w8CAwEAAaNUMFIwDgYDVR0PAQH/BAQDAgKk
MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wGgYDVR0RBBMw
EYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCl+yJgekN3dMbR
mxs7B8VSFv5a7zElDvTnagX3pSBHq7fLf45qVHOmZK/esgD3K8H5Kvft6u100b1j
4TLn2oFYVMME8BV0qNl5wgynNrJs131G3jgxcrgqu9NdlFpWZm8S+DCHo+h1ZH4Z
LmlUt2uvwCbmZK/e6U0ZDICDKRwMaC6LdUCfLfn9F+ACpPTpAZBVbi0rpAYimBDf
j2QZJBWD9tV5xUVSLEmqFvi42g2khK43HFu9N35vPIqyrl4Gh5x3erZR7t8pGEu0
kOiqfCmV1GHL8egxYew4wJ1P6TzhYNk/7vpiJxrwPs3vW+WXaSBFdvoV1qQ6onjm
CxG31l+z
-----END CERTIFICATE-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA3z9ep5/aKjNQxctEK8ecZz5IxoQO5b+Iz2f8gvrwXXV6wBHq
1eFqJFBF0Swz0zJ/INi32O4T5ctozKqFMBfAr1jNQsTgFNVsCv246DEu3JaykDi5
Z/GFyuoKpBNFzRT3lkScBVKPK0MBe0tDI/5SWczOYyEOnNG4fVheqbqoHTsbnI4n
I8qQOuoTKpSyOoEmYGRQmJh9LH3kl6f7hnOVJHvA6yjF6LT1BjK+jcMx9wk7xZvw
hAWM5PSiGEEUFHe/Uc45Ppsq6FQeJ/cr5JOC4wSEeuAAWZwVbc09Dci3ZLIp8YFn
yduZID3a2OiNAQVQepEqnRRbI5u5SY0CjVTXDwIDAQABAoIBACw3LNQeQONiznie
TZ4uJrf8CgXnWdv/F2WcvtJiSQD5p5oq8kvyHUeb7ngDPTBzK+KhiagZXy+AHf2L
OF3SFoOkHuM+gvMdYgy7O8ghFZry7eLKmU4Q8+LAf+MHPifkIzVL2Wrkcx6qYry8
p0uVr1HB0o6nmXFNyDBrNDSBl5JSJ+IyvtPr7ow5iO2iZLh0nV5CfM65vX2ESkvD
+uil1uFfLdmNkfItK/0oTLngiXzJBCgPzTwBnKGlmoYzWvO/CMMIEkv0tdy/b1l9
BTXiuRQEBy+CzSmoPyNnBCE/SOhZLH8+eGzsnlaty66AKWA1EwSjq7lDO1MOAL5Y
dPqwk8ECgYEA51T895d707Hl2/ggpEP1Wg8p96nz2iCt903WIOLn2X/9su2mNu5r
+Xtkd39ZYUuJIIja8hyx3q9E761jSI/F3lr4jwhTYO15aNvyD9S0supqASVodT33
VKYxrFH2PbRfb7RyuTUjlusJeP0QFz7hZ81eooYcgqkv8Gim14Y6XgkCgYEA9w2w
P5bTEPHweGCJ8I9AgCGUsg39x23qwN8xkxKb6jQcj6wHBpSw/yAPpAZ/1o+GFWDO
xiiNfqc+pLHAgPwEY2iUFKKJtKaS1kFIljTK353HVrDcqviCa4GCpSlBRi4xBkfi
vxS81eKaf8ChoiqfOuz3g6dHl6n3RGoQ8KpgUlcCgYBzbJh8AX2rdww14WyICdCW
CxLpnEcsAzpKNvAsoIsGnzI64REaP4RoiwTqCwTR4xqcvSxhuaeWcOV4oY70Wahk
9gcndwQDTPpTM8tn0r4Gt6gEwmGIfk62UeZfENZIm4My/Vpwxu7nEoc7cylgL+PQ
I0yg00HOgBSHY/A7gaIF4QKBgQDwjfaQZEaOGFYCkFWf04yFdq03lmIF/qP3Oxwl
TZhdOnKY/nM02DFjqY8xMlblz4hKZqHP1wq3SRe4+48qyLlpJhoR4ZXePdd6IcUQ
5MSpahL/+WRUYXd0QH26Xeo98JoxuGszjXi1dljjjeiUY5X5pWT4XzhZl9i5V+G4
xNzXLwKBgHtH/cPeR5O5gSHG+Fi5Sb/Ip6YYg00N8vtGwYYyc2i/uqz1N20igHJY
df7D5eYRIqrhBUVxqaqqs43oa1fi7CIFITYmof+qpxzRWKq9PPFc8D9mV0/03lba
0+i0kAvJB76WBiX48z8h+Rbc0IrZRDrVz9fk4Yfh+gHT4KDPmuII
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,241 @@
package testutils
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"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",
},
},
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: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
}
// RemoveContainer removes with force a container by it's container ID.
func RemoveContainer(dcli *client.Client, ctrID string) error {
return dcli.ContainerRemove(context.Background(), ctrID,
types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
})
}
// 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
}

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

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

@ -0,0 +1,103 @@
package main
import (
"errors"
"fmt"
"github.com/genuinetools/reg/clair"
"github.com/genuinetools/reg/repoutils"
"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")
}
if c.Int("fixable-threshold") < 0 {
return errors.New("fixable threshold must be a positive integer")
}
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
},
}