├── .gitignore ├── .travis.yml ├── README.md ├── main.go └── vendor └── vendor.json /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* 2 | dist/* 3 | !vendor/vendor.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: required 3 | services: 4 | - docker 5 | go: 6 | - 1.8.1 7 | before_install: 8 | - go get -u github.com/kardianos/govendor 9 | - go get github.com/mitchellh/gox 10 | - export GO_VERSION=$(go version | cut -d' ' -f3 | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+') 11 | install: 12 | - govendor sync 13 | script: 14 | - go build main.go 15 | after_success: 16 | # Build most binaries with GOX 17 | - gox -os "linux windows darwin" -arch "amd64 386" -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" 18 | # Rename the Linux binary so that it's clearly based on gnu/glibc 19 | - mv dist/copy-docker-image_linux_amd64 dist/copy-docker-image_linux_amd64_gnu 20 | # Build a Musl based binary for Apline Linux based users 21 | - docker run --rm -v $GOPATH:/go -w /go/src/github.com/mdlavin/copy-docker-image quay.io/mdlavin/go-alpine-musl:$GO_VERSION 22 | - mv copy-docker-image dist/copy-docker-image_linux_amd64_musl 23 | deploy: 24 | provider: releases 25 | api_key: 26 | secure: KsGWr0SutbYTNTaTO9nnYZUpO6Pki+oh1MsOGIquhJUBvDtT0VxHvRTIttA3Ziv/xJf6BKiSLBI885gC4/zavohqgk0WqtAQyiNtfmv/5o2fenRbgqjz75ak+pvOw2CyCKeRuo7R32FK1S0HKk4dTQua79toZjRyZcY3gTX2vzEHrEdRQSp8WQyHCfHE60de+sDGQZxQ0bcnfrMy4cSfSJ7hlpTywwJpBhURib1nY1q6RSotOrJBhmhTpBTEgn1AiIF51cYdtxmJJA4MrO6JEpyiciQvLrUquS/3w8k0+NmMYik0G12ECM3p6e/l9Qzb0tkMjPYvDWBMTHw4r/lObiJFV9V/Mc6iaP0PKyjKHEdx49ywMN0xspfEhm1H6R6YsxSEBGrdBvU7vhCDQh7TBZZJ7SqcW1jkNfpRY7wgTg4WCUzMJB8TD6rWEl+/LLb70uVjOKJaz0kXkDYc+RRG8+C2r3NPabfQ/pDBZoZvP87E2vyQ9M5Fo7vm8CIXDUF6VBHX8t4drCY5pTEN7UE3gH15An1S5njN+Kd4VMqeHfySO0ARceSZXiQcsgyZ3k/cVfX3rqDxXcAJYyli7IMX9QuPaGbtkDFejZqIWlrLs2EsQui/4r3RhQNVpUHtQjMlxQfa46TI9oScAnoMfDjY4S82nuIm328lKmAcRRNNQVw= 27 | file: 28 | - dist/copy-docker-image_darwin_amd64 29 | - dist/copy-docker-image_darwin_386 30 | - dist/copy-docker-image_linux_amd64_gnu 31 | - dist/copy-docker-image_linux_amd64_musl 32 | - dist/copy-docker-image_linux_386 33 | - dist/copy-docker-image_windows_amd64.exe 34 | - dist/copy-docker-image_windows_386.exe 35 | on: 36 | repo: mdlavin/copy-docker-image 37 | tags: true 38 | skip_cleanup: true 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # copy-docker-image - Copy docker images without a full docker installation 2 | [![Build Status](https://travis-ci.org/mdlavin/copy-docker-image.svg?branch=master)](https://travis-ci.org/mdlavin/copy-docker-image) 3 | 4 | ## Overview 5 | 6 | When doing automated deployments, especially when using AWS ECR in multiple accounts, you might want to copy images from one registry to another without the need for a full docker installation. At LifeOmic we wanted to orchestrate the copying of images while executing inside a container without exposing a full Docker socket just for image manipulation. 7 | 8 | To copy an image between two anonymous repositories, you can use a command line like: 9 | 10 | ``` 11 | $ copy-docker-image --src-repo http://registry1/ --dest-repo http://registry2 --repo project 12 | ``` 13 | 14 | To specify an image tag, just add a --tag argument like: 15 | 16 | ``` 17 | $ copy-docker-image --src-repo http://registry1/ --dest-repo http://registry2 --repo project --tag v1 18 | ``` 19 | 20 | ## Integration with AWS ECR 21 | 22 | Because copy to AWS ECR was common a special URL format was added to automatically look up the right HTTPS URL and authorization token. Assuming a AWS CLI profile has been created for your account you can use a command like: 23 | 24 | ``` 25 | $ copy-docker-image --src-repo http://registry1/ --dest-repo ecr: --repo project 26 | ``` 27 | 28 | ## Installation 29 | 30 | Pre-built binaries for tagged releases are available on the [releases page](https://github.com/mdlavin/copy-docker-image/releases). 31 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Matt Lavin 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/base64" 19 | "fmt" 20 | "github.com/alecthomas/kingpin" 21 | "github.com/aws/aws-sdk-go/aws" 22 | "github.com/aws/aws-sdk-go/aws/session" 23 | "github.com/aws/aws-sdk-go/service/ecr" 24 | "github.com/docker/distribution/manifest/schema1" 25 | "github.com/heroku/docker-registry-client/registry" 26 | "io" 27 | "io/ioutil" 28 | "os" 29 | "strings" 30 | ) 31 | 32 | func moveLayerUsingFile(srcHub *registry.Registry, destHub *registry.Registry, srcRepo string, destRepo string, layer schema1.FSLayer, file *os.File) error { 33 | layerDigest := layer.BlobSum 34 | 35 | srcImageReader, err := srcHub.DownloadLayer(srcRepo, layerDigest) 36 | if err != nil { 37 | return fmt.Errorf("Failure while starting the download of an image layer. %v", err) 38 | } 39 | 40 | _, err = io.Copy(file, srcImageReader) 41 | if err != nil { 42 | return fmt.Errorf("Failure while copying the image layer to a temp file. %v", err) 43 | } 44 | srcImageReader.Close() 45 | file.Sync() 46 | 47 | imageReadStream, err := os.Open(file.Name()) 48 | if err != nil { 49 | return fmt.Errorf("Failed to open temporary image layer for uploading. %v", err) 50 | } 51 | err = destHub.UploadLayer(destRepo, layerDigest, imageReadStream) 52 | imageReadStream.Close() 53 | if err != nil { 54 | return fmt.Errorf("Failure while uploading the image. %v", err) 55 | } 56 | 57 | return nil 58 | } 59 | 60 | func migrateLayer(srcHub *registry.Registry, destHub *registry.Registry, srcRepo string, destRepo string, layer schema1.FSLayer) error { 61 | fmt.Println("Checking if manifest layer exists in destination registery") 62 | 63 | layerDigest := layer.BlobSum 64 | hasLayer, err := destHub.HasLayer(destRepo, layerDigest) 65 | if err != nil { 66 | return fmt.Errorf("Failure while checking if the destination registry contained an image layer. %v", err) 67 | } 68 | 69 | if !hasLayer { 70 | fmt.Println("Need to upload layer", layerDigest, "to the destination") 71 | tempFile, err := ioutil.TempFile("", "docker-image") 72 | if err != nil { 73 | return fmt.Errorf("Failure while creating temporary file for an image layer download. %v", err) 74 | } 75 | 76 | err = moveLayerUsingFile(srcHub, destHub, srcRepo, destRepo, layer, tempFile) 77 | removeErr := os.Remove(tempFile.Name()) 78 | if removeErr != nil { 79 | // Print the error but don't fail the whole migration just because of a leaked temp file 80 | fmt.Printf("Failed to remove image layer temp file %s. %v", tempFile.Name(), removeErr) 81 | } 82 | 83 | return err 84 | } else { 85 | fmt.Println("Layer already exists in the destination") 86 | return nil 87 | } 88 | } 89 | 90 | type RepositoryArguments struct { 91 | RegistryURL *string 92 | Repository *string 93 | Tag *string 94 | } 95 | 96 | func buildRegistryArguments(argPrefix string, argDescription string) RepositoryArguments { 97 | registryURLName := fmt.Sprintf("%s-url", argPrefix) 98 | registryURLDescription := fmt.Sprintf("URL of %s registry", argDescription) 99 | registryURLArg := kingpin.Flag(registryURLName, registryURLDescription).String() 100 | 101 | repositoryName := fmt.Sprintf("%s-repo", argPrefix) 102 | repositoryDescription := fmt.Sprintf("Name of the %s repository", argDescription) 103 | repositoryArg := kingpin.Flag(repositoryName, repositoryDescription).String() 104 | 105 | tagName := fmt.Sprintf("%s-tag", argPrefix) 106 | tagDescription := fmt.Sprintf("Name of the %s tag", argDescription) 107 | tagArg := kingpin.Flag(tagName, tagDescription).String() 108 | 109 | return RepositoryArguments{ 110 | RegistryURL: registryURLArg, 111 | Repository: repositoryArg, 112 | Tag: tagArg, 113 | } 114 | } 115 | 116 | func connectToRegistry(args RepositoryArguments) (*registry.Registry, error) { 117 | origUrl := *args.RegistryURL 118 | url := origUrl 119 | username := "" 120 | password := "" 121 | 122 | if strings.HasPrefix(url, "ecr:") { 123 | registryId := strings.TrimPrefix(url, "ecr:") 124 | 125 | sess, err := session.NewSession() 126 | if err != nil { 127 | return nil, fmt.Errorf("Failed to create new AWS SDK session. %v", err) 128 | } 129 | svc := ecr.New(sess) 130 | params := &ecr.GetAuthorizationTokenInput{ 131 | RegistryIds: []*string{ 132 | aws.String(registryId), // Required 133 | }, 134 | } 135 | 136 | resp, err := svc.GetAuthorizationToken(params) 137 | if err != nil { 138 | return nil, fmt.Errorf("Failed to get ECR authorization token for registry %s. %v", registryId, err) 139 | } 140 | 141 | decoded, err := base64.StdEncoding.DecodeString(*resp.AuthorizationData[0].AuthorizationToken) 142 | if err != nil { 143 | return nil, fmt.Errorf("Failed to decode base64 encoded authorization data for ECR registry %s. %v", registryId, err) 144 | } 145 | 146 | parts := strings.Split(string(decoded), ":") 147 | 148 | url = *resp.AuthorizationData[0].ProxyEndpoint 149 | username = parts[0] 150 | password = parts[1] 151 | } 152 | 153 | registry, err := registry.New(url, username, password) 154 | if err != nil { 155 | return nil, fmt.Errorf("Failed to create registry connection for %s. %v", origUrl, err) 156 | } 157 | 158 | err = registry.Ping() 159 | if err != nil { 160 | return nil, fmt.Errorf("Failed to ping registry %s as a connection test. %v", origUrl, err) 161 | } 162 | 163 | return registry, nil 164 | } 165 | 166 | func main() { 167 | exitCode := 0 168 | defer func() { 169 | os.Exit(exitCode) 170 | }() 171 | 172 | srcArgs := buildRegistryArguments("src", "source") 173 | destArgs := buildRegistryArguments("dest", "destination") 174 | repoArg := kingpin.Flag("repo", "The repository in the source and the destination. Values provided by --src-repo or --dest-tag will override this value").String() 175 | tagArg := kingpin.Flag("tag", "The tag name in the source and the destination. Values provided by --src-tag or --dest-tag will override this value").Default("latest").String() 176 | kingpin.Parse() 177 | 178 | if *srcArgs.Repository == "" { 179 | srcArgs.Repository = repoArg 180 | } 181 | if *destArgs.Repository == "" { 182 | destArgs.Repository = repoArg 183 | } 184 | 185 | if *srcArgs.Tag == "" { 186 | srcArgs.Tag = tagArg 187 | } 188 | if *destArgs.Tag == "" { 189 | destArgs.Tag = tagArg 190 | } 191 | 192 | if *srcArgs.Repository == "" { 193 | fmt.Printf("A source repository name is required either with --src-repo or --repo") 194 | exitCode = -1 195 | return 196 | } 197 | 198 | if *destArgs.Repository == "" { 199 | fmt.Printf("A destination repository name is required either with --dest-repo or --repo") 200 | exitCode = -1 201 | return 202 | } 203 | 204 | srcHub, err := connectToRegistry(srcArgs) 205 | if err != nil { 206 | fmt.Printf("Failed to establish a connection to the source registry. %v", err) 207 | exitCode = -1 208 | return 209 | } 210 | 211 | destHub, err := connectToRegistry(destArgs) 212 | if err != nil { 213 | fmt.Printf("Failed to establish a connection to the destination registry. %v", err) 214 | exitCode = -1 215 | return 216 | } 217 | 218 | manifest, err := srcHub.Manifest(*srcArgs.Repository, *srcArgs.Tag) 219 | if err != nil { 220 | fmt.Printf("Failed to fetch the manifest for %s/%s:%s. %v", srcHub.URL, *srcArgs.Repository, *srcArgs.Tag, err) 221 | exitCode = -1 222 | return 223 | } 224 | 225 | for _, layer := range manifest.FSLayers { 226 | err := migrateLayer(srcHub, destHub, *srcArgs.Repository, *destArgs.Repository, layer) 227 | if err != nil { 228 | fmt.Printf("Failed to migrate image layer. %v", err) 229 | exitCode = -1 230 | return 231 | } 232 | } 233 | 234 | err = destHub.PutManifest(*destArgs.Repository, *destArgs.Tag, manifest) 235 | if err != nil { 236 | fmt.Printf("Failed to upload manifest to %s/%s:%s. %v", destHub.URL, *destArgs.Repository, *destArgs.Tag, err) 237 | exitCode = -1 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "", 4 | "package": [ 5 | { 6 | "checksumSHA1": "CEonWSn+KAO6bqzJCvPU0o9HhtY=", 7 | "path": "github.com/Sirupsen/logrus", 8 | "revision": "10f801ebc38b33738c9d17d50860f484a0988ff5", 9 | "revisionTime": "2017-03-17T14:32:14Z" 10 | }, 11 | { 12 | "checksumSHA1": "NVK14tpLb26kPzIX67+uDXS4/y4=", 13 | "path": "github.com/alecthomas/assert", 14 | "revision": "561411bbb7e98b005e520f4c0f491c2d23782048", 15 | "revisionTime": "2016-08-29T03:56:08Z" 16 | }, 17 | { 18 | "checksumSHA1": "8I+BkG7C9re9G+SCa7NR+q6wwyc=", 19 | "path": "github.com/alecthomas/colour", 20 | "revision": "60882d9e27213e8552dcff6328914fe4c2b44bc9", 21 | "revisionTime": "2016-05-24T08:22:31Z" 22 | }, 23 | { 24 | "checksumSHA1": "rW3ec6js0vX6SGxgDyadpGi7MUg=", 25 | "path": "github.com/alecthomas/kingpin", 26 | "revision": "e9044be3ab2a8e11d4e1f418d12f0790d57e8d70", 27 | "revisionTime": "2016-08-29T10:30:05Z", 28 | "version": "v2.2.3", 29 | "versionExact": "v2.2.3" 30 | }, 31 | { 32 | "path": "github.com/alecthomas/kingpin#v2.2.3", 33 | "revision": "" 34 | }, 35 | { 36 | "checksumSHA1": "QgdLcw5SRYg14F8LcgOkrr5iTw0=", 37 | "path": "github.com/alecthomas/repr", 38 | "revision": "d44565ca483f0ed23c2ac9cda62c966b116e77a3", 39 | "revisionTime": "2016-08-29T00:29:23Z" 40 | }, 41 | { 42 | "checksumSHA1": "eL44HknVGyM9pPBY/Lz9t5WUvpw=", 43 | "path": "github.com/alecthomas/template", 44 | "revision": "a0175ee3bccc567396460bf5acd36800cb10c49c", 45 | "revisionTime": "2016-04-05T07:15:01Z" 46 | }, 47 | { 48 | "checksumSHA1": "pcfwb2aFV05C9VEiW1npaPelbI0=", 49 | "path": "github.com/alecthomas/template/parse", 50 | "revision": "a0175ee3bccc567396460bf5acd36800cb10c49c", 51 | "revisionTime": "2016-04-05T07:15:01Z" 52 | }, 53 | { 54 | "checksumSHA1": "Js9yeJtM74TuQ4fJDDa/NtFMv34=", 55 | "path": "github.com/alecthomas/units", 56 | "revision": "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a", 57 | "revisionTime": "2015-10-22T06:55:26Z" 58 | }, 59 | { 60 | "checksumSHA1": "eoqKeJpIqUxPdmMGXJUtIV6YVtY=", 61 | "path": "github.com/aws/aws-sdk-go/aws", 62 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 63 | "revisionTime": "2017-04-08T20:12:58Z" 64 | }, 65 | { 66 | "checksumSHA1": "Y9W+4GimK4Fuxq+vyIskVYFRnX4=", 67 | "path": "github.com/aws/aws-sdk-go/aws/awserr", 68 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 69 | "revisionTime": "2017-04-08T20:12:58Z" 70 | }, 71 | { 72 | "checksumSHA1": "MfvvTPp96r7BWhNyHkMR99tPXhY=", 73 | "path": "github.com/aws/aws-sdk-go/aws/awsutil", 74 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 75 | "revisionTime": "2017-04-08T20:12:58Z" 76 | }, 77 | { 78 | "checksumSHA1": "iThCyNRL/oQFD9CF2SYgBGl+aww=", 79 | "path": "github.com/aws/aws-sdk-go/aws/client", 80 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 81 | "revisionTime": "2017-04-08T20:12:58Z" 82 | }, 83 | { 84 | "checksumSHA1": "ieAJ+Cvp/PKv1LpUEnUXpc3OI6E=", 85 | "path": "github.com/aws/aws-sdk-go/aws/client/metadata", 86 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 87 | "revisionTime": "2017-04-08T20:12:58Z" 88 | }, 89 | { 90 | "checksumSHA1": "oqks2E/FEKyhf35weCzaD7QbMTw=", 91 | "path": "github.com/aws/aws-sdk-go/aws/corehandlers", 92 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 93 | "revisionTime": "2017-04-08T20:12:58Z" 94 | }, 95 | { 96 | "checksumSHA1": "1Y5lQ5DiXxKPe0iITIzx2wbItZc=", 97 | "path": "github.com/aws/aws-sdk-go/aws/credentials", 98 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 99 | "revisionTime": "2017-04-08T20:12:58Z" 100 | }, 101 | { 102 | "checksumSHA1": "vNezRqMaW2P4eroQOw+X8VvdRLk=", 103 | "path": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds", 104 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 105 | "revisionTime": "2017-04-08T20:12:58Z" 106 | }, 107 | { 108 | "checksumSHA1": "3gcwv0Ttdiby3736WSTLqc6hsNk=", 109 | "path": "github.com/aws/aws-sdk-go/aws/credentials/endpointcreds", 110 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 111 | "revisionTime": "2017-04-08T20:12:58Z" 112 | }, 113 | { 114 | "checksumSHA1": "cQk39tx00VRyXFMTetVl81SFTQc=", 115 | "path": "github.com/aws/aws-sdk-go/aws/credentials/stscreds", 116 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 117 | "revisionTime": "2017-04-08T20:12:58Z" 118 | }, 119 | { 120 | "checksumSHA1": "m4eWUyny2MMk6jGxgYZXq9U7yi0=", 121 | "path": "github.com/aws/aws-sdk-go/aws/defaults", 122 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 123 | "revisionTime": "2017-04-08T20:12:58Z" 124 | }, 125 | { 126 | "checksumSHA1": "Vs428vDEBSyEMdEqyW7a6qxSNCw=", 127 | "path": "github.com/aws/aws-sdk-go/aws/ec2metadata", 128 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 129 | "revisionTime": "2017-04-08T20:12:58Z" 130 | }, 131 | { 132 | "checksumSHA1": "Zgxq4iNCaREMbnXnwF1IyyJqvks=", 133 | "path": "github.com/aws/aws-sdk-go/aws/endpoints", 134 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 135 | "revisionTime": "2017-04-08T20:12:58Z" 136 | }, 137 | { 138 | "checksumSHA1": "HsPu0TomZ1iBoT9xJxMqcPYl7GI=", 139 | "path": "github.com/aws/aws-sdk-go/aws/request", 140 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 141 | "revisionTime": "2017-04-08T20:12:58Z" 142 | }, 143 | { 144 | "checksumSHA1": "Kv6SKvyNOEyhE5NDRpq0Pl7UbFA=", 145 | "path": "github.com/aws/aws-sdk-go/aws/session", 146 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 147 | "revisionTime": "2017-04-08T20:12:58Z" 148 | }, 149 | { 150 | "checksumSHA1": "CgM0Xb1WPeTur4KYHmfiVhK3iVg=", 151 | "path": "github.com/aws/aws-sdk-go/aws/signer/v4", 152 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 153 | "revisionTime": "2017-04-08T20:12:58Z" 154 | }, 155 | { 156 | "checksumSHA1": "VF6UbXmnQq/6hpqQEBgrMVby6cU=", 157 | "path": "github.com/aws/aws-sdk-go/awstesting", 158 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 159 | "revisionTime": "2017-04-08T20:12:58Z" 160 | }, 161 | { 162 | "checksumSHA1": "iOAj9X968hli6R2oCTjFVQPH5Ug=", 163 | "path": "github.com/aws/aws-sdk-go/awstesting/mock", 164 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 165 | "revisionTime": "2017-04-08T20:12:58Z" 166 | }, 167 | { 168 | "checksumSHA1": "MIp5H1niMhCZFAwYsjJx9NxmHIc=", 169 | "path": "github.com/aws/aws-sdk-go/awstesting/unit", 170 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 171 | "revisionTime": "2017-04-08T20:12:58Z" 172 | }, 173 | { 174 | "checksumSHA1": "YVb6IGbsYv0bBxIflqDLow+vnmY=", 175 | "path": "github.com/aws/aws-sdk-go/private/protocol", 176 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 177 | "revisionTime": "2017-04-08T20:12:58Z" 178 | }, 179 | { 180 | "checksumSHA1": "EgOJpBwiLQGdhy0syET+Qrh/iw8=", 181 | "path": "github.com/aws/aws-sdk-go/private/protocol/ec2query", 182 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 183 | "revisionTime": "2017-04-08T20:12:58Z" 184 | }, 185 | { 186 | "checksumSHA1": "Kv0l+/YRDWr7wdo6QuTiZAtsbkk=", 187 | "path": "github.com/aws/aws-sdk-go/private/protocol/json/jsonutil", 188 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 189 | "revisionTime": "2017-04-08T20:12:58Z" 190 | }, 191 | { 192 | "checksumSHA1": "YltcVf/XohcHxzpGfaH3hK9AXoI=", 193 | "path": "github.com/aws/aws-sdk-go/private/protocol/jsonrpc", 194 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 195 | "revisionTime": "2017-04-08T20:12:58Z" 196 | }, 197 | { 198 | "checksumSHA1": "2phBX1u2Aq2YNFNS+pJ2NDv8c1E=", 199 | "path": "github.com/aws/aws-sdk-go/private/protocol/query", 200 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 201 | "revisionTime": "2017-04-08T20:12:58Z" 202 | }, 203 | { 204 | "checksumSHA1": "Drt1JfLMa0DQEZLWrnMlTWaIcC8=", 205 | "path": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil", 206 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 207 | "revisionTime": "2017-04-08T20:12:58Z" 208 | }, 209 | { 210 | "checksumSHA1": "2V+Q+QAP6NuwPl50GLJNHnPUTpk=", 211 | "path": "github.com/aws/aws-sdk-go/private/protocol/rest", 212 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 213 | "revisionTime": "2017-04-08T20:12:58Z" 214 | }, 215 | { 216 | "checksumSHA1": "t3MpUb9nOUMxm1OBijhJ/QRHneM=", 217 | "path": "github.com/aws/aws-sdk-go/private/protocol/restjson", 218 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 219 | "revisionTime": "2017-04-08T20:12:58Z" 220 | }, 221 | { 222 | "checksumSHA1": "9pLEncUqChzzZr3KjfQ81yo0LA8=", 223 | "path": "github.com/aws/aws-sdk-go/private/protocol/restxml", 224 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 225 | "revisionTime": "2017-04-08T20:12:58Z" 226 | }, 227 | { 228 | "checksumSHA1": "SvtQIBR4oTPeRTCdIcsUf8LlkDs=", 229 | "path": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil", 230 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 231 | "revisionTime": "2017-04-08T20:12:58Z" 232 | }, 233 | { 234 | "checksumSHA1": "01b4hmyUzoReoOyEDylDinWBSdA=", 235 | "path": "github.com/aws/aws-sdk-go/private/util", 236 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 237 | "revisionTime": "2017-04-08T20:12:58Z" 238 | }, 239 | { 240 | "checksumSHA1": "zAiOuU0DuHfsBjB7NwyvEQhSzEY=", 241 | "path": "github.com/aws/aws-sdk-go/service/cloudfront", 242 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 243 | "revisionTime": "2017-04-08T20:12:58Z" 244 | }, 245 | { 246 | "checksumSHA1": "aL8nF6gVDtbTKvpWKARCZCZT890=", 247 | "path": "github.com/aws/aws-sdk-go/service/dynamodb", 248 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 249 | "revisionTime": "2017-04-08T20:12:58Z" 250 | }, 251 | { 252 | "checksumSHA1": "FLZfqbUVl+dWyrz+stgDy9KGxyQ=", 253 | "path": "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute", 254 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 255 | "revisionTime": "2017-04-08T20:12:58Z" 256 | }, 257 | { 258 | "checksumSHA1": "oZ9nV3besQdwyejmAVOh3Rhstek=", 259 | "path": "github.com/aws/aws-sdk-go/service/ec2", 260 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 261 | "revisionTime": "2017-04-08T20:12:58Z" 262 | }, 263 | { 264 | "checksumSHA1": "Yv0cWL0NHm541sP3fUUaTpp31iU=", 265 | "path": "github.com/aws/aws-sdk-go/service/ecr", 266 | "revision": "200fde3f632bb9f036ec0b8cb29fbc8588155ea5", 267 | "revisionTime": "2017-04-07T21:28:24Z", 268 | "version": "v1.8.11", 269 | "versionExact": "v1.8.11" 270 | }, 271 | { 272 | "checksumSHA1": "DXUC3RnwFUQ4GqmCFoXaegLhnTE=", 273 | "path": "github.com/aws/aws-sdk-go/service/elastictranscoder", 274 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 275 | "revisionTime": "2017-04-08T20:12:58Z" 276 | }, 277 | { 278 | "checksumSHA1": "7fllfa9mgSMQGkKcQQjuTICHpTk=", 279 | "path": "github.com/aws/aws-sdk-go/service/kinesis", 280 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 281 | "revisionTime": "2017-04-08T20:12:58Z" 282 | }, 283 | { 284 | "checksumSHA1": "ZT+czT3XwdoUvwpWbxnMHE8Qy00=", 285 | "path": "github.com/aws/aws-sdk-go/service/route53", 286 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 287 | "revisionTime": "2017-04-08T20:12:58Z" 288 | }, 289 | { 290 | "checksumSHA1": "boyZXE6i5mOBXSUdgTqx77rG1fw=", 291 | "path": "github.com/aws/aws-sdk-go/service/s3", 292 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 293 | "revisionTime": "2017-04-08T20:12:58Z" 294 | }, 295 | { 296 | "checksumSHA1": "s3Iy4ZOgz5UZpFZS1+9NhkP0YkA=", 297 | "path": "github.com/aws/aws-sdk-go/service/sqs", 298 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 299 | "revisionTime": "2017-04-08T20:12:58Z" 300 | }, 301 | { 302 | "checksumSHA1": "qEfVbSM27+ww5jx0sjBYroPl6eg=", 303 | "path": "github.com/aws/aws-sdk-go/service/sts", 304 | "revision": "c555a87c32af3840e6ab7c106e7aee3dac87f99e", 305 | "revisionTime": "2017-04-08T20:12:58Z" 306 | }, 307 | { 308 | "checksumSHA1": "WRu+mhZ2PfQu27qRr69HVGjtN4Q=", 309 | "path": "github.com/davecgh/go-spew/spew", 310 | "revision": "346938d642f2ec3594ed81d874461961cd0faa76", 311 | "revisionTime": "2016-10-29T20:57:26Z" 312 | }, 313 | { 314 | "checksumSHA1": "a2yC46a1qsJomgY6rb+FkTFiqmE=", 315 | "path": "github.com/davecgh/go-spew/spew/testdata", 316 | "revision": "346938d642f2ec3594ed81d874461961cd0faa76", 317 | "revisionTime": "2016-10-29T20:57:26Z" 318 | }, 319 | { 320 | "checksumSHA1": "1wtjU4A1TJw5mWf/8zQWXxgG/Hg=", 321 | "path": "github.com/docker/distribution", 322 | "revision": "a25b9ef0c9fe242ac04bb20d3a028442b7d266b6", 323 | "revisionTime": "2017-04-05T23:17:09Z", 324 | "version": "v2.6.1", 325 | "versionExact": "v2.6.1" 326 | }, 327 | { 328 | "checksumSHA1": "W1Rw0QZdsmLEU7KKSiVoigMAHgI=", 329 | "path": "github.com/docker/distribution/context", 330 | "revision": "a25b9ef0c9fe242ac04bb20d3a028442b7d266b6", 331 | "revisionTime": "2017-04-05T23:17:09Z", 332 | "version": "v2.6.1", 333 | "versionExact": "v2.6.1" 334 | }, 335 | { 336 | "checksumSHA1": "66UlS6CE2174+Nj5T3wyLX3J/6U=", 337 | "path": "github.com/docker/distribution/digest", 338 | "revision": "a25b9ef0c9fe242ac04bb20d3a028442b7d266b6", 339 | "revisionTime": "2017-04-05T23:17:09Z", 340 | "version": "v2.6.1", 341 | "versionExact": "v2.6.1" 342 | }, 343 | { 344 | "checksumSHA1": "oYy5Q1HBImMQvh9t96cmNzWar80=", 345 | "path": "github.com/docker/distribution/manifest", 346 | "revision": "a25b9ef0c9fe242ac04bb20d3a028442b7d266b6", 347 | "revisionTime": "2017-04-05T23:17:09Z", 348 | "version": "v2.6.1", 349 | "versionExact": "v2.6.1" 350 | }, 351 | { 352 | "checksumSHA1": "lz+qrc9j4ytm6FhJnpoCtjt3ewo=", 353 | "path": "github.com/docker/distribution/manifest/schema1", 354 | "revision": "a25b9ef0c9fe242ac04bb20d3a028442b7d266b6", 355 | "revisionTime": "2017-04-05T23:17:09Z", 356 | "version": "v2.6.1", 357 | "versionExact": "v2.6.1" 358 | }, 359 | { 360 | "checksumSHA1": "LflJIWvJ5QscTOdbnm+3SghPgCo=", 361 | "path": "github.com/docker/distribution/manifest/schema2", 362 | "revision": "a25b9ef0c9fe242ac04bb20d3a028442b7d266b6", 363 | "revisionTime": "2017-04-05T23:17:09Z", 364 | "version": "v2.6.1", 365 | "versionExact": "v2.6.1" 366 | }, 367 | { 368 | "checksumSHA1": "HxYUMz3WOV7b11zazR1FZFuQCHE=", 369 | "path": "github.com/docker/distribution/reference", 370 | "revision": "a25b9ef0c9fe242ac04bb20d3a028442b7d266b6", 371 | "revisionTime": "2017-04-05T23:17:09Z", 372 | "version": "v2.6.1", 373 | "versionExact": "v2.6.1" 374 | }, 375 | { 376 | "checksumSHA1": "tNWbcdclT5h9ygMIsq3VG05S6B0=", 377 | "path": "github.com/docker/distribution/uuid", 378 | "revision": "a25b9ef0c9fe242ac04bb20d3a028442b7d266b6", 379 | "revisionTime": "2017-04-05T23:17:09Z", 380 | "version": "v2.6.1", 381 | "versionExact": "v2.6.1" 382 | }, 383 | { 384 | "checksumSHA1": "SEVXNIcbfW8UK108uGqG1Lk81zo=", 385 | "path": "github.com/docker/libtrust", 386 | "revision": "aabc10ec26b754e797f9028f4589c5b7bd90dc20", 387 | "revisionTime": "2016-07-08T17:25:13Z" 388 | }, 389 | { 390 | "checksumSHA1": "ej5H4I19BGtX42Dfe1ozH1Gcd6k=", 391 | "path": "github.com/docker/libtrust/testutil", 392 | "revision": "aabc10ec26b754e797f9028f4589c5b7bd90dc20", 393 | "revisionTime": "2016-07-08T17:25:13Z" 394 | }, 395 | { 396 | "checksumSHA1": "dfFmjWgt0Dz0WR5NZ0YVsxBTmY4=", 397 | "path": "github.com/go-ini/ini", 398 | "revision": "e7fea39b01aea8d5671f6858f0532f56e8bff3a5", 399 | "revisionTime": "2017-03-28T15:39:02Z" 400 | }, 401 | { 402 | "checksumSHA1": "axNNGK9AXZzNOCfYfG0qiYfdTYc=", 403 | "path": "github.com/gopherjs/gopherjs/js", 404 | "revision": "3496c6f94e1b945f3fc9580f6982675c0a74cd6c", 405 | "revisionTime": "2017-03-27T17:11:18Z" 406 | }, 407 | { 408 | "checksumSHA1": "Thg9sicXr23/02y0VqmmJoEZIzI=", 409 | "path": "github.com/gorilla/context", 410 | "revision": "08b5f424b9271eedf6f9f0ce86cb9396ed337a42", 411 | "revisionTime": "2016-08-17T18:46:32Z" 412 | }, 413 | { 414 | "checksumSHA1": "6ZwQ9Tcj4jf/muB2KpPpG8lArZs=", 415 | "path": "github.com/gorilla/mux", 416 | "revision": "599cba5e7b6137d46ddf58fb1765f5d928e69604", 417 | "revisionTime": "2017-02-28T22:43:54Z" 418 | }, 419 | { 420 | "checksumSHA1": "gAqIxmhElSUq+G+oqaPf2VM1UjU=", 421 | "path": "github.com/heroku/docker-registry-client/registry", 422 | "revision": "95467b6cacee2a06f112a3cf7e47a70fad6000cf", 423 | "revisionTime": "2017-02-17T08:40:47Z" 424 | }, 425 | { 426 | "checksumSHA1": "TDeiKiCXvRCyaC5uBryv5Av5SMk=", 427 | "path": "github.com/jmespath/go-jmespath", 428 | "revision": "bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d", 429 | "revisionTime": "2016-08-03T19:07:31Z" 430 | }, 431 | { 432 | "checksumSHA1": "3tpCIywCgmASqlnNyJ16XXSpzMw=", 433 | "path": "github.com/jtolds/gls", 434 | "revision": "bb0351aa7eb6f322f32667d51375f26a2bca6628", 435 | "revisionTime": "2016-12-28T00:43:38Z" 436 | }, 437 | { 438 | "checksumSHA1": "cAY8WWlvwXakO9nUpQbqNW13JkI=", 439 | "path": "github.com/mattn/go-isatty", 440 | "revision": "fc9e8d8ef48496124e79ae0df75490096eccf6fe", 441 | "revisionTime": "2017-03-22T23:44:13Z" 442 | }, 443 | { 444 | "checksumSHA1": "RZOdTSZN/PgcTqko5LzIAzw+UT4=", 445 | "path": "github.com/pmezard/go-difflib/difflib", 446 | "revision": "792786c7400a136282c1664665ae0a8db921c6c2", 447 | "revisionTime": "2016-01-10T10:55:54Z" 448 | }, 449 | { 450 | "checksumSHA1": "m7jW9/geqk1flilV96ghdtmyups=", 451 | "path": "github.com/sergi/go-diff/diffmatchpatch", 452 | "revision": "feef008d51ad2b3778f85d387ccf91735543008d", 453 | "revisionTime": "2017-04-09T07:17:39Z" 454 | }, 455 | { 456 | "checksumSHA1": "BpFbfnxs4jrjGldU6aMIMcrDmXM=", 457 | "path": "github.com/smartystreets/assertions", 458 | "revision": "c9ee7d9e9a2aeec0bee7c4a516f3e0ad7cb7e558", 459 | "revisionTime": "2017-02-13T16:53:18Z" 460 | }, 461 | { 462 | "checksumSHA1": "Vzb+dEH/LTYbvr8RXHmt6xJHz04=", 463 | "path": "github.com/smartystreets/assertions/internal/go-render/render", 464 | "revision": "c9ee7d9e9a2aeec0bee7c4a516f3e0ad7cb7e558", 465 | "revisionTime": "2017-02-13T16:53:18Z" 466 | }, 467 | { 468 | "checksumSHA1": "r6FauVdOTFnwYQgrKGFuWUbIAJE=", 469 | "path": "github.com/smartystreets/assertions/internal/oglematchers", 470 | "revision": "c9ee7d9e9a2aeec0bee7c4a516f3e0ad7cb7e558", 471 | "revisionTime": "2017-02-13T16:53:18Z" 472 | }, 473 | { 474 | "checksumSHA1": "KYSYYlW1zv3HfpbNSLJpDYj5rJE=", 475 | "path": "github.com/smartystreets/goconvey/convey", 476 | "revision": "3bd662eac601ad6436e64776af2e112069eb2edc", 477 | "revisionTime": "2017-01-10T12:00:22Z" 478 | }, 479 | { 480 | "checksumSHA1": "2x3M0H7g69Yul0dydYqLklCFDDA=", 481 | "path": "github.com/smartystreets/goconvey/convey/gotest", 482 | "revision": "3bd662eac601ad6436e64776af2e112069eb2edc", 483 | "revisionTime": "2017-01-10T12:00:22Z" 484 | }, 485 | { 486 | "checksumSHA1": "/Vf/BK8DEzagv141VuY7P1UtI40=", 487 | "path": "github.com/smartystreets/goconvey/convey/reporting", 488 | "revision": "3bd662eac601ad6436e64776af2e112069eb2edc", 489 | "revisionTime": "2017-01-10T12:00:22Z" 490 | }, 491 | { 492 | "checksumSHA1": "KryCFMSBWiYWd9cvnc/OaKONvnU=", 493 | "path": "github.com/stevvooe/resumable", 494 | "revision": "2aaf90b2ceea5072cb503ef2a620b08ff3119870", 495 | "revisionTime": "2017-03-02T21:34:56Z" 496 | }, 497 | { 498 | "checksumSHA1": "4Fi0rZ/ZuvdIx/A2K4iU0QGYYMA=", 499 | "path": "github.com/stevvooe/resumable/sha256", 500 | "revision": "2aaf90b2ceea5072cb503ef2a620b08ff3119870", 501 | "revisionTime": "2017-03-02T21:34:56Z" 502 | }, 503 | { 504 | "checksumSHA1": "8rxOh1Vlti1bzoIy6mH5eL0RYsI=", 505 | "path": "github.com/stretchr/testify/assert", 506 | "revision": "4d4bfba8f1d1027c4fdbe371823030df51419987", 507 | "revisionTime": "2017-01-30T11:31:45Z" 508 | }, 509 | { 510 | "checksumSHA1": "6kF7mPXIAVaHHZu4qaljLMgeF80=", 511 | "path": "github.com/stretchr/testify/require", 512 | "revision": "4d4bfba8f1d1027c4fdbe371823030df51419987", 513 | "revisionTime": "2017-01-30T11:31:45Z" 514 | }, 515 | { 516 | "checksumSHA1": "ujkdYz/AOSZ4dVskaUGW9g/V/dM=", 517 | "path": "golang.org/x/net/context", 518 | "revision": "d1e1b351919c6738fdeb9893d5c998b161464f0c", 519 | "revisionTime": "2016-12-29T22:47:41Z" 520 | }, 521 | { 522 | "checksumSHA1": "4K9ZcSGRZ8EPpsPhGUpgUJb+5sw=", 523 | "path": "golang.org/x/sys/unix", 524 | "revision": "f3918c30c5c2cb527c0b071a27c35120a6c0719a", 525 | "revisionTime": "2017-04-05T16:58:12Z" 526 | } 527 | ], 528 | "rootPath": "copy-docker-image" 529 | } 530 | --------------------------------------------------------------------------------