25
.github/workflows/go.yml
vendored
Normal file
25
.github/workflows/go.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
|
||||
- name: Build
|
||||
run: go build -v
|
||||
|
||||
- name: Test
|
||||
run: go test -v
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
dist/
|
||||
/base-cli-plugin
|
||||
/go-skill.iml
|
||||
29
.goreleaser.yaml
Normal file
29
.goreleaser.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
release:
|
||||
prerelease: auto
|
||||
draft: true
|
||||
|
||||
builds:
|
||||
- binary: &binary docker-base
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
# set the modified timestamp on the output binary to the git timestamp to ensure a reproducible build
|
||||
mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}'
|
||||
env: &build-env
|
||||
- CGO_ENABLED=0
|
||||
ldflags: &build-ldflags |
|
||||
-w
|
||||
-s
|
||||
-extldflags '-static'
|
||||
-X github.com/docker/base-cli-plugin/internal.version={{.Version}}
|
||||
-X github.com/docker/base-cli-plugin/internal.commit={{.Commit}}
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
195
LICENSE
Normal file
195
LICENSE
Normal file
@@ -0,0 +1,195 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the
|
||||
copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other
|
||||
entities that control, are controlled by, or are under common control
|
||||
with that entity. For the purposes of this definition, "control" means
|
||||
(i) the power, direct or indirect, to cause the direction or management
|
||||
of such entity, whether by contract or otherwise, or (ii) ownership of
|
||||
fifty percent (50%) or more of the outstanding shares, or (iii) beneficial
|
||||
ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation source,
|
||||
and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation
|
||||
or translation of a Source form, including but not limited to compiled
|
||||
object code, generated documentation, and conversions to other media
|
||||
types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object
|
||||
form, made available under the License, as indicated by a copyright notice
|
||||
that is included in or attached to the work (an example is provided in
|
||||
the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form,
|
||||
that is based on (or derived from) the Work and for which the editorial
|
||||
revisions, annotations, elaborations, or other modifications represent,
|
||||
as a whole, an original work of authorship. For the purposes of this
|
||||
License, Derivative Works shall not include works that remain separable
|
||||
from, or merely link (or bind by name) to the interfaces of, the Work and
|
||||
Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original
|
||||
version of the Work and any modifications or additions to that Work or
|
||||
Derivative Works thereof, that is intentionally submitted to Licensor for
|
||||
inclusion in the Work by the copyright owner or by an individual or Legal
|
||||
Entity authorized to submit on behalf of the copyright owner. For the
|
||||
purposes of this definition, "submitted" means any form of electronic,
|
||||
verbal, or written communication sent to the Licensor or its representatives,
|
||||
including but not limited to communication on electronic mailing lists,
|
||||
source code control systems, and issue tracking systems that are managed
|
||||
by, or on behalf of, the Licensor for the purpose of discussing and
|
||||
improving the Work, but excluding communication that is conspicuously
|
||||
marked or otherwise designated in writing by the copyright owner as "Not
|
||||
a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on
|
||||
behalf of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual, worldwide,
|
||||
non-exclusive, no-charge, royalty-free, irrevocable copyright license to
|
||||
reproduce, prepare Derivative Works of, publicly display, publicly
|
||||
perform, sublicense, and distribute the Work and such Derivative Works
|
||||
in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this
|
||||
License, each Contributor hereby grants to You a perpetual, worldwide,
|
||||
non-exclusive, no-charge, royalty-free, irrevocable (except as stated in
|
||||
this section) patent license to make, have made, use, offer to sell,
|
||||
sell, import, and otherwise transfer the Work, where such license applies
|
||||
only to those patent claims licensable by such Contributor that are
|
||||
necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including
|
||||
a cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this
|
||||
License for that Work shall terminate as of the date such litigation is
|
||||
filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work
|
||||
or Derivative Works thereof in any medium, with or without modifications,
|
||||
and in Source or Object form, provided that You meet the following
|
||||
conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or Derivative
|
||||
Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works that
|
||||
You distribute, all copyright, patent, trademark, and attribution
|
||||
notices from the Source form of the Work, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one of
|
||||
the following places: within a NOTICE text file distributed as
|
||||
part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and do
|
||||
not modify the License. You may add Your own attribution notices
|
||||
within Derivative Works that You distribute, alongside or as an
|
||||
addendum to the NOTICE text from the Work, provided that such
|
||||
additional attribution notices cannot be construed as modifying
|
||||
the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions for
|
||||
use, reproduction, or distribution of Your modifications, or for any
|
||||
such Derivative Works as a whole, provided Your use, reproduction,
|
||||
and distribution of the Work otherwise complies with the conditions
|
||||
stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work by You
|
||||
to the Licensor shall be under the terms and conditions of this License,
|
||||
without any additional terms or conditions. Notwithstanding the above,
|
||||
nothing herein shall supersede or modify the terms of any separate license
|
||||
agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor, except
|
||||
as required for reasonable and customary use in describing the origin of
|
||||
the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed
|
||||
to in writing, Licensor provides the Work (and each Contributor provides
|
||||
its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
|
||||
OF ANY KIND, either express or implied, including, without limitation,
|
||||
any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY,
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for
|
||||
determining the appropriateness of using or redistributing the Work and
|
||||
assume any risks associated with Your exercise of permissions under this
|
||||
License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether
|
||||
in tort (including negligence), contract, or otherwise, unless required
|
||||
by applicable law (such as deliberate and grossly negligent acts) or
|
||||
agreed to in writing, shall any Contributor be liable to You for damages,
|
||||
including any direct, indirect, special, incidental, or consequential
|
||||
damages of any character arising as a result of this License or out of
|
||||
the use or inability to use the Work (including but not limited to damages
|
||||
for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the
|
||||
Work or Derivative Works thereof, You may choose to offer, and charge a
|
||||
fee for, acceptance of support, warranty, indemnity, or other liability
|
||||
obligations and/or rights consistent with this License. However, in
|
||||
accepting such obligations, You may act only on Your own behalf and on
|
||||
Your sole responsibility, not on behalf of any other Contributor, and
|
||||
only if You agree to indemnify, defend, and hold each Contributor harmless
|
||||
for any liability incurred by, or claims asserted against, such Contributor
|
||||
by reason of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should
|
||||
be enclosed in the appropriate comment syntax for the file format. We
|
||||
also recommend that a file or class name and description of purpose be
|
||||
included on the same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
65
README.md
Normal file
65
README.md
Normal file
@@ -0,0 +1,65 @@
|
||||
**Note:** This repository is not an officially supported Docker project.
|
||||
|
||||
# `docker base` Docker CLI plugin
|
||||
|
||||
Docker CLI plugin to detect base images of local or remote container images.
|
||||
|
||||

|
||||
|
||||
The `docker base detect` command shows the following data about detected base images:
|
||||
|
||||
* Image name and tags
|
||||
* Image digest
|
||||
* Image vulnerabilities
|
||||
* Age of image
|
||||
* Link to the commit or Dockerfile from which the image was created
|
||||
* `Docker Official Image`, `Verified Publisher` or `Sponsored OSS` - if the image is trusted content; either from Docker Official Images, Verified Publishers or Sponsored OSS
|
||||
* `unsupported tag` - if the tag in use is not supported any longer (only for Docker Official Images)
|
||||
* `tag moved` - if the tag in use has moved in the meantime; a new image was pushed using the same tag
|
||||
|
||||
## Installation
|
||||
|
||||
To install, run the following command in your terminal:
|
||||
|
||||
```shell
|
||||
$ curl -sSfL https://raw.githubusercontent.com/docker/base-cli-plugin/main/install.sh | sh -s --
|
||||
```
|
||||
|
||||
Alternatively, you can install manually by following these steps:
|
||||
|
||||
* Download the plugin binary from the [release page](https://github.com/docker/base-cli-plugin/releases/latest)
|
||||
* Unzip the archive
|
||||
* Copy/move the binary into `$HOME/.docker/cli-plugins`
|
||||
|
||||
## Usage
|
||||
|
||||
### `docker base detect`
|
||||
|
||||
To detect base images for local or remote images, use the following command:
|
||||
|
||||
```shell
|
||||
$ docker base detect <image>
|
||||
```
|
||||
|
||||
`<image>` can either be a local image id or fully qualified image name from a remote registry.
|
||||
|
||||
### `docker base login`
|
||||
|
||||
To authenticate with the Atomist data plane, use the following command:
|
||||
|
||||
```shell
|
||||
$ docker base login --workspace <workspace id> --api-key <api key>
|
||||
```
|
||||
|
||||
Authentication is not required. If not authenticated, the plugin will only use public data from Docker Official Images,
|
||||
Docker Verified Publishers or Docker-sponsored Open Source.
|
||||
|
||||
Visit [dso.docker.com](https://dso.docker.com/r/auth/integrations) to obtain a `workspace id` and `api key`.
|
||||
|
||||
### `docker base logout`
|
||||
|
||||
To remove the authentication to the Atomist data plane, use the following command:
|
||||
|
||||
```shell
|
||||
$ docker base logout
|
||||
```
|
||||
203
commands/detect.go
Normal file
203
commands/detect.go
Normal file
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright © 2022 Docker, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/docker/base-cli-plugin/query"
|
||||
"github.com/docker/base-cli-plugin/registry"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/fatih/color"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/identity"
|
||||
"github.com/xeonx/timeago"
|
||||
)
|
||||
|
||||
func Detect(dockerCli command.Cli, image string, workspace string, apiKey string) error {
|
||||
s := spinner.New(spinner.CharSets[14], 100*time.Millisecond)
|
||||
s.Suffix = fmt.Sprintf(" Retrieving layer information for image %s", image)
|
||||
s.Color("blue")
|
||||
s.Start()
|
||||
defer s.Stop()
|
||||
|
||||
green := color.New(color.FgGreen).SprintFunc()
|
||||
cyan := color.New(color.FgCyan).SprintFunc()
|
||||
blue := color.New(color.BgBlue, color.FgHiWhite).SprintFunc()
|
||||
red := color.New(color.BgRed, color.FgHiWhite).SprintFunc()
|
||||
|
||||
digests, err := registry.DigestForImage(dockerCli, image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainIds := make([]digest.Digest, 0)
|
||||
for i := range digests {
|
||||
label := ""
|
||||
if i == 0 {
|
||||
label = "layer 0"
|
||||
} else {
|
||||
label = fmt.Sprintf("layers 0-%d", i)
|
||||
}
|
||||
chainIds = append(chainIds, digests[i])
|
||||
chainId := identity.ChainID(chainIds)
|
||||
s.Suffix = fmt.Sprintf(" Finding matching base images for %s", label)
|
||||
s.Restart()
|
||||
images, err := query.ForBaseImage(chainId, workspace, apiKey)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if images != nil {
|
||||
bi := make([]string, len(*images))
|
||||
for ix := range *images {
|
||||
image := (*images)[ix]
|
||||
e := " "
|
||||
if image.Repository.Host != "hub.docker.com" {
|
||||
e += image.Repository.Host + "/"
|
||||
}
|
||||
e += image.Repository.Name
|
||||
e = green(e)
|
||||
if len(image.Tags) > 0 {
|
||||
tags := make([]string, len(image.Tags))
|
||||
for i := range image.Tags {
|
||||
tags[i] = cyan(image.Tags[i])
|
||||
}
|
||||
e += ":" + strings.Join(tags, ", ")
|
||||
|
||||
}
|
||||
if oc := officialContent(image); oc != "" {
|
||||
e += " " + blue(oc)
|
||||
if st := supportedTag(image); st != "" {
|
||||
e += " " + red(st)
|
||||
}
|
||||
}
|
||||
if ct := currentTag(image); ct != "" {
|
||||
e += " " + red(ct)
|
||||
}
|
||||
e += "\n " + image.Digest
|
||||
if cve := renderVulnerabilities(image); cve != "" {
|
||||
e += " " + red(cve)
|
||||
}
|
||||
e += " " + timeago.NoMax(timeago.English).Format(image.CreatedAt)
|
||||
if url := renderCommit(image); url != "" {
|
||||
e += "\n " + url
|
||||
}
|
||||
bi[ix] = e
|
||||
}
|
||||
s.Stop()
|
||||
fmt.Printf("Base image for %s\n%s\n\n", label, strings.Join(bi, "\n"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func officialContent(image query.Image) string {
|
||||
switch image.Repository.Badge {
|
||||
case "open_source":
|
||||
return " Sponsored OSS "
|
||||
case "verified_publisher":
|
||||
return " Verified Publisher "
|
||||
default:
|
||||
if image.Repository.Host == "hub.docker.com" && !strings.Contains(image.Repository.Name, "/") {
|
||||
return " Docker Official Image "
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func supportedTag(image query.Image) string {
|
||||
if tagCount := len(image.Repository.SupportedTags); tagCount > 0 {
|
||||
unsupportedTags := make([]string, 0)
|
||||
for _, tag := range image.Tags {
|
||||
if !contains(image.Repository.SupportedTags, tag) {
|
||||
unsupportedTags = append(unsupportedTags, tag)
|
||||
}
|
||||
}
|
||||
if len(unsupportedTags) == len(image.Tags) {
|
||||
return " unsupported tag "
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func currentTag(image query.Image) string {
|
||||
currentTags := make([]string, 0)
|
||||
for _, tag := range image.Tag {
|
||||
currentTags = append(currentTags, tag.Name)
|
||||
}
|
||||
for _, manifestList := range image.ManifestList {
|
||||
for _, tag := range manifestList.Tags {
|
||||
currentTags = append(currentTags, tag.Name)
|
||||
}
|
||||
}
|
||||
if len(currentTags) > 0 {
|
||||
for _, tag := range image.Tags {
|
||||
if contains(currentTags, tag) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
return " tag moved "
|
||||
}
|
||||
|
||||
func contains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func renderCommit(image query.Image) string {
|
||||
if image.Commit.Sha != "" {
|
||||
url := fmt.Sprintf("https://github.com/%s/%s", image.Commit.Repo.Org.Name, image.Commit.Repo.Name)
|
||||
if image.File.Path != "" {
|
||||
url = fmt.Sprintf("%s/blob/%s/%s", url, image.Commit.Sha, image.File.Path)
|
||||
}
|
||||
return url
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func renderVulnerabilities(image query.Image) string {
|
||||
if len(image.Report) > 0 {
|
||||
report := image.Report[0]
|
||||
parts := make([]string, 0)
|
||||
if report.Critical > 0 {
|
||||
parts = append(parts, " C"+strconv.FormatInt(report.Critical, 10))
|
||||
}
|
||||
if report.High > 0 {
|
||||
parts = append(parts, " H"+strconv.FormatInt(report.High, 10))
|
||||
}
|
||||
if report.Medium > 0 {
|
||||
parts = append(parts, " M"+strconv.FormatInt(report.Medium, 10))
|
||||
}
|
||||
if report.Low > 0 {
|
||||
parts = append(parts, " L"+strconv.FormatInt(report.Low, 10))
|
||||
}
|
||||
if len(parts) > 0 {
|
||||
return strings.Join(parts, " ") + " "
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
BIN
docs/screenshot.png
Normal file
BIN
docs/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 512 KiB |
58
go.mod
Normal file
58
go.mod
Normal file
@@ -0,0 +1,58 @@
|
||||
module github.com/docker/base-cli-plugin
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/briandowns/spinner v1.19.0
|
||||
github.com/docker/cli v20.10.17+incompatible
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/google/go-containerregistry v0.5.1
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/xeonx/timeago v1.0.0-rc5
|
||||
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.17 // indirect
|
||||
github.com/Microsoft/hcsshim v0.9.4 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.4.1 // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker v20.10.17+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/fvbommel/sortorder v1.0.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.0 // indirect
|
||||
github.com/gorilla/mux v1.7.3 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/miekg/pkcs11 v1.0.3 // indirect
|
||||
github.com/moby/sys/mount v0.3.3 // indirect
|
||||
github.com/moby/sys/mountinfo v0.6.2 // indirect
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.7.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.10.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
|
||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
)
|
||||
725
install.sh
Normal file
725
install.sh
Normal file
@@ -0,0 +1,725 @@
|
||||
#!/bin/sh
|
||||
|
||||
# note: we require errors to propagate (don't set -e)
|
||||
|
||||
# Copyright © 2022 Docker, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set -u
|
||||
|
||||
PROJECT_NAME="base-cli-plugin"
|
||||
OWNER=docker
|
||||
REPO="${PROJECT_NAME}"
|
||||
GITHUB_DOWNLOAD_PREFIX=https://github.com/${OWNER}/${REPO}/releases/download
|
||||
INSTALL_SH_BASE_URL=https://raw.githubusercontent.com/${OWNER}/${PROJECT_NAME}
|
||||
BINARY="docker-base"
|
||||
DOCKER_HOME=${DOCKER_HOME:-~/.docker}
|
||||
DEFAULT_INSTALL_DIR=${DOCKER_HOME}/cli-plugins
|
||||
PROGRAM_ARGS=$@
|
||||
|
||||
# do not change the name of this parameter (this must always be backwards compatible)
|
||||
DOWNLOAD_TAG_INSTALL_SCRIPT=${DOWNLOAD_TAG_INSTALL_SCRIPT:-true}
|
||||
|
||||
#
|
||||
# usage [script-name]
|
||||
#
|
||||
usage() (
|
||||
this=$1
|
||||
cat <<EOF
|
||||
$this: download go binaries for ${OWNER}/${REPO}
|
||||
|
||||
Usage: $this [-b] dir [-d] [tag]
|
||||
-b the installation directory (defaults to ${DEFAULT_INSTALL_DIR})
|
||||
-d turns on debug logging
|
||||
-dd turns on trace logging
|
||||
[tag] the specific release to use (if missing, then the latest will be used)
|
||||
EOF
|
||||
exit 2
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# https://github.com/client9/shlib - portable posix shell functions
|
||||
# Public domain - http://unlicense.org
|
||||
# https://github.com/client9/shlib/blob/master/LICENSE.md
|
||||
# but credit (and pull requests) appreciated.
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
is_command() (
|
||||
command -v "$1" >/dev/null
|
||||
)
|
||||
|
||||
echo_stderr() (
|
||||
echo "$@" 1>&2
|
||||
)
|
||||
|
||||
_logp=2
|
||||
log_set_priority() {
|
||||
_logp="$1"
|
||||
}
|
||||
|
||||
log_priority() (
|
||||
if test -z "$1"; then
|
||||
echo "$_logp"
|
||||
return
|
||||
fi
|
||||
[ "$1" -le "$_logp" ]
|
||||
)
|
||||
|
||||
init_colors() {
|
||||
RED=''
|
||||
BLUE=''
|
||||
PURPLE=''
|
||||
BOLD=''
|
||||
RESET=''
|
||||
# check if stdout is a terminal
|
||||
if test -t 1 && is_command tput; then
|
||||
# see if it supports colors
|
||||
ncolors=$(tput colors)
|
||||
if test -n "$ncolors" && test $ncolors -ge 8; then
|
||||
RED='\033[0;31m'
|
||||
BLUE='\033[0;34m'
|
||||
PURPLE='\033[0;35m'
|
||||
BOLD='\033[1m'
|
||||
RESET='\033[0m'
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
init_colors
|
||||
|
||||
log_tag() (
|
||||
case $1 in
|
||||
0) echo "${RED}${BOLD}[error]${RESET}" ;;
|
||||
1) echo "${RED}[warn]${RESET}" ;;
|
||||
2) echo "[info]${RESET}" ;;
|
||||
3) echo "${BLUE}[debug]${RESET}" ;;
|
||||
4) echo "${PURPLE}[trace]${RESET}" ;;
|
||||
*) echo "[$1]" ;;
|
||||
esac
|
||||
)
|
||||
|
||||
|
||||
log_trace_priority=4
|
||||
log_trace() (
|
||||
priority=$log_trace_priority
|
||||
log_priority "$priority" || return 0
|
||||
echo_stderr "$(log_tag $priority)" "${@}" "${RESET}"
|
||||
)
|
||||
|
||||
log_debug_priority=3
|
||||
log_debug() (
|
||||
priority=$log_debug_priority
|
||||
log_priority "$priority" || return 0
|
||||
echo_stderr "$(log_tag $priority)" "${@}" "${RESET}"
|
||||
)
|
||||
|
||||
log_info_priority=2
|
||||
log_info() (
|
||||
priority=$log_info_priority
|
||||
log_priority "$priority" || return 0
|
||||
echo_stderr "$(log_tag $priority)" "${@}" "${RESET}"
|
||||
)
|
||||
|
||||
log_warn_priority=1
|
||||
log_warn() (
|
||||
priority=$log_warn_priority
|
||||
log_priority "$priority" || return 0
|
||||
echo_stderr "$(log_tag $priority)" "${@}" "${RESET}"
|
||||
)
|
||||
|
||||
log_err_priority=0
|
||||
log_err() (
|
||||
priority=$log_err_priority
|
||||
log_priority "$priority" || return 0
|
||||
echo_stderr "$(log_tag $priority)" "${@}" "${RESET}"
|
||||
)
|
||||
|
||||
uname_os_check() (
|
||||
os=$1
|
||||
case "$os" in
|
||||
darwin) return 0 ;;
|
||||
dragonfly) return 0 ;;
|
||||
freebsd) return 0 ;;
|
||||
linux) return 0 ;;
|
||||
android) return 0 ;;
|
||||
nacl) return 0 ;;
|
||||
netbsd) return 0 ;;
|
||||
openbsd) return 0 ;;
|
||||
plan9) return 0 ;;
|
||||
solaris) return 0 ;;
|
||||
windows) return 0 ;;
|
||||
esac
|
||||
log_err "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib"
|
||||
return 1
|
||||
)
|
||||
|
||||
uname_arch_check() (
|
||||
arch=$1
|
||||
case "$arch" in
|
||||
386) return 0 ;;
|
||||
amd64) return 0 ;;
|
||||
arm64) return 0 ;;
|
||||
armv5) return 0 ;;
|
||||
armv6) return 0 ;;
|
||||
armv7) return 0 ;;
|
||||
ppc64) return 0 ;;
|
||||
ppc64le) return 0 ;;
|
||||
mips) return 0 ;;
|
||||
mipsle) return 0 ;;
|
||||
mips64) return 0 ;;
|
||||
mips64le) return 0 ;;
|
||||
s390x) return 0 ;;
|
||||
amd64p32) return 0 ;;
|
||||
esac
|
||||
log_err "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib"
|
||||
return 1
|
||||
)
|
||||
|
||||
unpack() (
|
||||
archive=$1
|
||||
|
||||
log_trace "unpack(archive=${archive})"
|
||||
|
||||
case "${archive}" in
|
||||
*.tar.gz | *.tgz) tar --no-same-owner -xzf "${archive}" ;;
|
||||
*.tar) tar --no-same-owner -xf "${archive}" ;;
|
||||
*.zip) unzip -q "${archive}" ;;
|
||||
*.dmg) extract_from_dmg "${archive}" ;;
|
||||
*)
|
||||
log_err "unpack unknown archive format for ${archive}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
)
|
||||
|
||||
extract_from_dmg() (
|
||||
dmg_file=$1
|
||||
|
||||
mount_point="/Volumes/tmp-dmg"
|
||||
hdiutil attach -quiet -nobrowse -mountpoint "${mount_point}" "${dmg_file}"
|
||||
cp -fR "${mount_point}/." ./
|
||||
hdiutil detach -quiet -force "${mount_point}"
|
||||
)
|
||||
|
||||
http_download_curl() (
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
header=$3
|
||||
|
||||
log_trace "http_download_curl(local_file=$local_file, source_url=$source_url, header=$header)"
|
||||
|
||||
if [ -z "$header" ]; then
|
||||
code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
|
||||
else
|
||||
code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
|
||||
fi
|
||||
|
||||
if [ "$code" != "200" ]; then
|
||||
log_err "received HTTP status=$code for url='$source_url'"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
)
|
||||
|
||||
http_download_wget() (
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
header=$3
|
||||
|
||||
log_trace "http_download_wget(local_file=$local_file, source_url=$source_url, header=$header)"
|
||||
|
||||
if [ -z "$header" ]; then
|
||||
wget -q -O "$local_file" "$source_url"
|
||||
else
|
||||
wget -q --header "$header" -O "$local_file" "$source_url"
|
||||
fi
|
||||
)
|
||||
|
||||
http_download() (
|
||||
log_debug "http_download(url=$2)"
|
||||
if is_command curl; then
|
||||
http_download_curl "$@"
|
||||
return
|
||||
elif is_command wget; then
|
||||
http_download_wget "$@"
|
||||
return
|
||||
fi
|
||||
log_err "http_download unable to find wget or curl"
|
||||
return 1
|
||||
)
|
||||
|
||||
http_copy() (
|
||||
tmp=$(mktemp)
|
||||
http_download "${tmp}" "$1" "$2" || return 1
|
||||
body=$(cat "$tmp")
|
||||
rm -f "${tmp}"
|
||||
echo "$body"
|
||||
)
|
||||
|
||||
hash_sha256() (
|
||||
TARGET=${1:-/dev/stdin}
|
||||
if is_command gsha256sum; then
|
||||
hash=$(gsha256sum "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command sha256sum; then
|
||||
hash=$(sha256sum "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command shasum; then
|
||||
hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command openssl; then
|
||||
hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f a
|
||||
else
|
||||
log_err "hash_sha256 unable to find command to compute sha-256 hash"
|
||||
return 1
|
||||
fi
|
||||
)
|
||||
|
||||
hash_sha256_verify() (
|
||||
TARGET=$1
|
||||
checksums=$2
|
||||
if [ -z "$checksums" ]; then
|
||||
log_err "hash_sha256_verify checksum file not specified in arg2"
|
||||
return 1
|
||||
fi
|
||||
BASENAME=${TARGET##*/}
|
||||
want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
|
||||
if [ -z "$want" ]; then
|
||||
log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
|
||||
return 1
|
||||
fi
|
||||
got=$(hash_sha256 "$TARGET")
|
||||
if [ "$want" != "$got" ]; then
|
||||
log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
|
||||
return 1
|
||||
fi
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# End of functions from https://github.com/client9/shlib
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
# asset_file_exists [path]
|
||||
#
|
||||
# returns 1 if the given file does not exist
|
||||
#
|
||||
asset_file_exists() (
|
||||
path="$1"
|
||||
if [ ! -f "${path}" ]; then
|
||||
return 1
|
||||
fi
|
||||
)
|
||||
|
||||
|
||||
# github_release_json [owner] [repo] [version]
|
||||
#
|
||||
# outputs release json string
|
||||
#
|
||||
github_release_json() (
|
||||
owner=$1
|
||||
repo=$2
|
||||
version=$3
|
||||
test -z "$version" && version="latest"
|
||||
giturl="https://github.com/${owner}/${repo}/releases/${version}"
|
||||
json=$(http_copy "$giturl" "Accept:application/json")
|
||||
|
||||
log_trace "github_release_json(owner=${owner}, repo=${repo}, version=${version}) returned '${json}'"
|
||||
|
||||
test -z "$json" && return 1
|
||||
echo "${json}"
|
||||
)
|
||||
|
||||
# extract_value [key-value-pair]
|
||||
#
|
||||
# outputs value from a colon delimited key-value pair
|
||||
#
|
||||
extract_value() (
|
||||
key_value="$1"
|
||||
IFS=':' read -r _ value << EOF
|
||||
${key_value}
|
||||
EOF
|
||||
echo "$value"
|
||||
)
|
||||
|
||||
# extract_json_value [json] [key]
|
||||
#
|
||||
# outputs value of the key from the given json string
|
||||
#
|
||||
extract_json_value() (
|
||||
json="$1"
|
||||
key="$2"
|
||||
key_value=$(echo "${json}" | grep -o "\"$key\":[^,]*[,}]" | tr -d '",}')
|
||||
|
||||
extract_value "$key_value"
|
||||
)
|
||||
|
||||
# github_release_tag [release-json]
|
||||
#
|
||||
# outputs release tag string
|
||||
#
|
||||
github_release_tag() (
|
||||
json="$1"
|
||||
tag=$(extract_json_value "${json}" "tag_name")
|
||||
test -z "$tag" && return 1
|
||||
echo "$tag"
|
||||
)
|
||||
|
||||
# download_github_release_checksums [release-url-prefix] [name] [version] [output-dir]
|
||||
#
|
||||
# outputs path to the downloaded checksums file
|
||||
#
|
||||
download_github_release_checksums() (
|
||||
download_url="$1"
|
||||
name="$2"
|
||||
version="$3"
|
||||
output_dir="$4"
|
||||
|
||||
log_trace "download_github_release_checksums(url=${download_url}, name=${name}, version=${version}, output_dir=${output_dir})"
|
||||
|
||||
checksum_filename=${name}_${version}_checksums.txt
|
||||
checksum_url=${download_url}/${checksum_filename}
|
||||
output_path="${output_dir}/${checksum_filename}"
|
||||
|
||||
http_download "${output_path}" "${checksum_url}" ""
|
||||
asset_file_exists "${output_path}"
|
||||
|
||||
log_trace "download_github_release_checksums() returned '${output_path}'"
|
||||
|
||||
echo "${output_path}"
|
||||
)
|
||||
|
||||
# search_for_asset [checksums-file-path] [name] [os] [arch] [format]
|
||||
#
|
||||
# outputs name of the asset to download
|
||||
#
|
||||
search_for_asset() (
|
||||
checksum_path="$1"
|
||||
name="$2"
|
||||
os="$3"
|
||||
arch="$4"
|
||||
format="$5"
|
||||
|
||||
log_trace "search_for_asset(checksum-path=${checksum_path}, name=${name}, os=${os}, arch=${arch}, format=${format})"
|
||||
|
||||
asset_glob="${name}_.*_${os}_${arch}.${format}"
|
||||
output_path=$(grep -o "${asset_glob}" "${checksum_path}" || true)
|
||||
|
||||
log_trace "search_for_asset() returned '${output_path}'"
|
||||
|
||||
echo "${output_path}"
|
||||
)
|
||||
|
||||
# uname_os
|
||||
#
|
||||
# outputs an adjusted os value
|
||||
#
|
||||
uname_os() (
|
||||
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
case "$os" in
|
||||
cygwin_nt*) os="windows" ;;
|
||||
mingw*) os="windows" ;;
|
||||
msys_nt*) os="windows" ;;
|
||||
esac
|
||||
|
||||
uname_os_check "$os"
|
||||
|
||||
log_trace "uname_os() returned '${os}'"
|
||||
|
||||
echo "$os"
|
||||
)
|
||||
|
||||
# uname_arch
|
||||
#
|
||||
# outputs an adjusted architecture value
|
||||
#
|
||||
uname_arch() (
|
||||
arch=$(uname -m)
|
||||
case $arch in
|
||||
x86_64) arch="amd64" ;;
|
||||
x86) arch="386" ;;
|
||||
i686) arch="386" ;;
|
||||
i386) arch="386" ;;
|
||||
aarch64) arch="arm64" ;;
|
||||
armv5*) arch="armv5" ;;
|
||||
armv6*) arch="armv6" ;;
|
||||
armv7*) arch="armv7" ;;
|
||||
esac
|
||||
|
||||
uname_arch_check "${arch}"
|
||||
|
||||
log_trace "uname_arch() returned '${arch}'"
|
||||
|
||||
echo "${arch}"
|
||||
)
|
||||
|
||||
# get_release_tag [owner] [repo] [tag]
|
||||
#
|
||||
# outputs tag string
|
||||
#
|
||||
get_release_tag() (
|
||||
owner="$1"
|
||||
repo="$2"
|
||||
tag="$3"
|
||||
|
||||
log_trace "get_release_tag(owner=${owner}, repo=${repo}, tag=${tag})"
|
||||
|
||||
json=$(github_release_json "${owner}" "${repo}" "${tag}")
|
||||
real_tag=$(github_release_tag "${json}")
|
||||
if test -z "${real_tag}"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_trace "get_release_tag() returned '${real_tag}'"
|
||||
|
||||
echo "${real_tag}"
|
||||
)
|
||||
|
||||
# tag_to_version [tag]
|
||||
#
|
||||
# outputs version string
|
||||
#
|
||||
tag_to_version() (
|
||||
tag="$1"
|
||||
value="${tag#v}"
|
||||
|
||||
log_trace "tag_to_version(tag=${tag}) returned '${value}'"
|
||||
|
||||
echo "$value"
|
||||
)
|
||||
|
||||
# get_binary_name [os] [arch] [default-name]
|
||||
#
|
||||
# outputs a the binary string name
|
||||
#
|
||||
get_binary_name() (
|
||||
os="$1"
|
||||
arch="$2"
|
||||
binary="$3"
|
||||
original_binary="${binary}"
|
||||
|
||||
case "${os}" in
|
||||
windows) binary="${binary}.exe" ;;
|
||||
esac
|
||||
|
||||
log_trace "get_binary_name(os=${os}, arch=${arch}, binary=${original_binary}) returned '${binary}'"
|
||||
|
||||
echo "${binary}"
|
||||
)
|
||||
|
||||
|
||||
# get_format_name [os] [arch] [default-format]
|
||||
#
|
||||
# outputs an adjusted file format
|
||||
#
|
||||
get_format_name() (
|
||||
os="$1"
|
||||
arch="$2"
|
||||
format="$3"
|
||||
original_format="${format}"
|
||||
|
||||
case ${os} in
|
||||
windows) format=zip ;;
|
||||
esac
|
||||
|
||||
log_trace "get_format_name(os=${os}, arch=${arch}, format=${original_format}) returned '${format}'"
|
||||
|
||||
echo "${format}"
|
||||
)
|
||||
|
||||
# download_and_install_asset [release-url-prefix] [download-path] [install-path] [name] [os] [arch] [version] [format] [binary]
|
||||
#
|
||||
# attempts to download the archive and install it to the given path.
|
||||
#
|
||||
download_and_install_asset() (
|
||||
download_url="$1"
|
||||
download_path="$2"
|
||||
install_path=$3
|
||||
name="$4"
|
||||
os="$5"
|
||||
arch="$6"
|
||||
version="$7"
|
||||
format="$8"
|
||||
binary="$9"
|
||||
|
||||
asset_filepath=$(download_asset "${download_url}" "${download_path}" "${name}" "${os}" "${arch}" "${version}" "${format}")
|
||||
|
||||
# don't continue if we couldn't download an asset
|
||||
if [ -z "${asset_filepath}" ]; then
|
||||
log_err "could not find release asset for os='${os}' arch='${arch}' format='${format}' "
|
||||
return 1
|
||||
fi
|
||||
|
||||
install_asset "${asset_filepath}" "${install_path}" "${binary}"
|
||||
)
|
||||
|
||||
# download_asset [release-url-prefix] [download-path] [name] [os] [arch] [version] [format] [binary]
|
||||
#
|
||||
# outputs the path to the downloaded asset asset_filepath
|
||||
#
|
||||
download_asset() (
|
||||
download_url="$1"
|
||||
destination="$2"
|
||||
name="$3"
|
||||
os="$4"
|
||||
arch="$5"
|
||||
version="$6"
|
||||
format="$7"
|
||||
|
||||
log_trace "download_asset(url=${download_url}, destination=${destination}, name=${name}, os=${os}, arch=${arch}, version=${version}, format=${format})"
|
||||
|
||||
checksums_filepath=$(download_github_release_checksums "${download_url}" "${name}" "${version}" "${destination}")
|
||||
|
||||
log_trace "checksums content:\n$(cat ${checksums_filepath})"
|
||||
|
||||
asset_filename=$(search_for_asset "${checksums_filepath}" "${name}" "${os}" "${arch}" "${format}")
|
||||
|
||||
# don't continue if we couldn't find a matching asset from the checksums file
|
||||
if [ -z "${asset_filename}" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
asset_url="${download_url}/${asset_filename}"
|
||||
asset_filepath="${destination}/${asset_filename}"
|
||||
http_download "${asset_filepath}" "${asset_url}" ""
|
||||
|
||||
hash_sha256_verify "${asset_filepath}" "${checksums_filepath}"
|
||||
|
||||
log_trace "download_asset_by_checksums_file() returned '${asset_filepath}'"
|
||||
|
||||
echo "${asset_filepath}"
|
||||
)
|
||||
|
||||
# install_asset [asset-path] [destination-path] [binary]
|
||||
#
|
||||
install_asset() (
|
||||
asset_filepath="$1"
|
||||
destination="$2"
|
||||
binary="$3"
|
||||
|
||||
log_trace "install_asset(asset=${asset_filepath}, destination=${destination}, binary=${binary})"
|
||||
|
||||
# don't continue if we don't have anything to install
|
||||
if [ -z "${asset_filepath}" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
archive_dir=$(dirname "${asset_filepath}")
|
||||
|
||||
# unarchive the downloaded archive to the temp dir
|
||||
(cd "${archive_dir}" && unpack "${asset_filepath}")
|
||||
|
||||
# create the destination dir
|
||||
test ! -d "${destination}" && install -d "${destination}"
|
||||
|
||||
# install the binary to the destination dir
|
||||
install "${archive_dir}/${binary}" "${destination}/"
|
||||
)
|
||||
|
||||
main() (
|
||||
# parse arguments
|
||||
|
||||
# note: never change default install directory (this must always be backwards compatible)
|
||||
install_dir=${install_dir:-${DEFAULT_INSTALL_DIR}}
|
||||
|
||||
# note: never change the program flags or arguments (this must always be backwards compatible)
|
||||
while getopts "b:dh?x" arg; do
|
||||
case "$arg" in
|
||||
b) install_dir="$OPTARG" ;;
|
||||
d)
|
||||
if [ "$_logp" = "$log_info_priority" ]; then
|
||||
# -d == debug
|
||||
log_set_priority $log_debug_priority
|
||||
else
|
||||
# -dd (or -ddd...) == trace
|
||||
log_set_priority $log_trace_priority
|
||||
fi
|
||||
;;
|
||||
h | \?) usage "$0" ;;
|
||||
x) set -x ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
set +u
|
||||
tag=$1
|
||||
|
||||
if [ "${install_dir}" = "${DEFAULT_INSTALL_DIR}" ]; then
|
||||
if [ ! -d "${DOCKER_HOME}" ]; then
|
||||
log_err "docker is not installed; refusing to install to '${install_dir}'"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "${tag}" ]; then
|
||||
log_debug "checking github for the current release tag"
|
||||
tag=""
|
||||
else
|
||||
log_debug "checking github for release tag='${tag}'"
|
||||
fi
|
||||
set -u
|
||||
|
||||
tag=$(get_release_tag "${OWNER}" "${REPO}" "${tag}")
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
log_err "unable to find tag='${tag}'"
|
||||
log_err "do not specify a version or select a valid version from https://github.com/${OWNER}/${REPO}/releases"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# run the application
|
||||
|
||||
version=$(tag_to_version "${tag}")
|
||||
os=$(uname_os)
|
||||
arch=$(uname_arch)
|
||||
format=$(get_format_name "${os}" "${arch}" "tar.gz")
|
||||
binary=$(get_binary_name "${os}" "${arch}" "${BINARY}")
|
||||
download_url="${GITHUB_DOWNLOAD_PREFIX}/${tag}"
|
||||
|
||||
# we always use the install.sh script that is associated with the tagged release. Why? the latest install.sh is not
|
||||
# guaranteed to be able to install every version of the application. We use the DOWNLOAD_TAG_INSTALL_SCRIPT env var
|
||||
# to indicate if we should continue processing with the existing script or to download the script from the given tag.
|
||||
if [ "${DOWNLOAD_TAG_INSTALL_SCRIPT}" = "true" ]; then
|
||||
export DOWNLOAD_TAG_INSTALL_SCRIPT=false
|
||||
log_info "fetching release script for tag='${tag}'"
|
||||
http_copy "${INSTALL_SH_BASE_URL}/${tag}/install.sh" "" | sh -s -- ${PROGRAM_ARGS}
|
||||
exit $?
|
||||
fi
|
||||
|
||||
log_info "using release tag='${tag}' version='${version}' os='${os}' arch='${arch}'"
|
||||
|
||||
download_dir=$(mktemp -d)
|
||||
trap 'rm -rf -- "$download_dir"' EXIT
|
||||
|
||||
log_debug "downloading files into ${download_dir}"
|
||||
|
||||
download_and_install_asset "${download_url}" "${download_dir}" "${install_dir}" "${PROJECT_NAME}" "${os}" "${arch}" "${version}" "${format}" "${binary}"
|
||||
|
||||
# don't continue if we couldn't install the asset
|
||||
if [ "$?" != "0" ]; then
|
||||
log_err "failed to install ${BINARY}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "installed ${install_dir}/${binary}"
|
||||
)
|
||||
|
||||
# entrypoint
|
||||
|
||||
set +u
|
||||
if [ -z "${TEST_INSTALL_SH}" ]; then
|
||||
set -u
|
||||
main "$@"
|
||||
fi
|
||||
set -u
|
||||
35
internal/version.go
Normal file
35
internal/version.go
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Package internal contains all build time metadata (version, build time, git commit, etc).
|
||||
*/
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// build-time arguments
|
||||
var (
|
||||
version = "n/a"
|
||||
commit = "n/a"
|
||||
)
|
||||
|
||||
// Version information from build time args and environment
|
||||
type Version struct {
|
||||
Version string
|
||||
Commit string
|
||||
GoVersion string
|
||||
Compiler string
|
||||
Platform string
|
||||
}
|
||||
|
||||
// FromBuild provides all version details
|
||||
func FromBuild() Version {
|
||||
return Version{
|
||||
Version: fmt.Sprintf("v%s", version),
|
||||
Commit: commit,
|
||||
GoVersion: runtime.Version(),
|
||||
Compiler: runtime.Compiler,
|
||||
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
|
||||
}
|
||||
}
|
||||
105
main.go
Normal file
105
main.go
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright © 2022 Docker, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/base-cli-plugin/commands"
|
||||
"github.com/docker/base-cli-plugin/internal"
|
||||
"github.com/docker/base-cli-plugin/query"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/plugin"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
|
||||
var (
|
||||
workspace, apiKey string
|
||||
)
|
||||
|
||||
logout := &cobra.Command{
|
||||
Use: "logout",
|
||||
Short: "Remove Atomist workspace authentication",
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
dockerCli.ConfigFile().SetPluginConfig("base", "workspace", "")
|
||||
dockerCli.ConfigFile().SetPluginConfig("base", "api-key", "")
|
||||
return dockerCli.ConfigFile().Save()
|
||||
},
|
||||
}
|
||||
|
||||
login := &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "Authenticate with an Atomist workspace",
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
if !query.CheckAuth(workspace, apiKey) {
|
||||
return errors.New("login failed")
|
||||
} else {
|
||||
dockerCli.ConfigFile().SetPluginConfig("base", "workspace", workspace)
|
||||
dockerCli.ConfigFile().SetPluginConfig("base", "api-key", apiKey)
|
||||
return dockerCli.ConfigFile().Save()
|
||||
}
|
||||
},
|
||||
}
|
||||
loginFlags := login.Flags()
|
||||
loginFlags.StringVar(&workspace, "workspace", "", "Atomist workspace")
|
||||
loginFlags.StringVar(&apiKey, "api-key", "", "Atomist API key")
|
||||
login.MarkFlagRequired("workspace")
|
||||
login.MarkFlagRequired("api-key")
|
||||
|
||||
base := &cobra.Command{
|
||||
Use: "detect [OPTIONS] IMAGE",
|
||||
Short: "Detect base images for a given image",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
if err := cmd.Usage(); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf(`"docker base detect" requires exactly 1 argument`)
|
||||
}
|
||||
if workspace == "" {
|
||||
workspace, _ = dockerCli.ConfigFile().PluginConfig("base", "workspace")
|
||||
}
|
||||
if apiKey == "" {
|
||||
apiKey, _ = dockerCli.ConfigFile().PluginConfig("base", "api-key")
|
||||
}
|
||||
|
||||
return commands.Detect(dockerCli, args[0], workspace, apiKey)
|
||||
},
|
||||
}
|
||||
baseFlags := base.Flags()
|
||||
baseFlags.StringVar(&workspace, "workspace", "", "Atomist workspace")
|
||||
baseFlags.StringVar(&apiKey, "api-key", "", "Atomist API key")
|
||||
base.MarkFlagRequired("image")
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "base",
|
||||
Short: "Identify base image",
|
||||
}
|
||||
|
||||
cmd.AddCommand(login, logout, base)
|
||||
return cmd
|
||||
},
|
||||
manager.Metadata{
|
||||
SchemaVersion: "0.1.0",
|
||||
Vendor: "Docker Inc.",
|
||||
Version: internal.FromBuild().Version,
|
||||
})
|
||||
}
|
||||
52
query/base_image_query.edn
Normal file
52
query/base_image_query.edn
Normal file
@@ -0,0 +1,52 @@
|
||||
;; Copyright © 2022 Docker, Inc.
|
||||
;;
|
||||
;; Licensed under the Apache License, Version 2.0 (the "License");
|
||||
;; you may not use this file except in compliance with the License.
|
||||
;; You may obtain a copy of the License at
|
||||
;;
|
||||
;; http://www.apache.org/licenses/LICENSE-2.0
|
||||
;;
|
||||
;; Unless required by applicable law or agreed to in writing, software
|
||||
;; distributed under the License is distributed on an "AS IS" BASIS,
|
||||
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
;; See the License for the specific language governing permissions and
|
||||
;; limitations under the License.
|
||||
|
||||
[:find
|
||||
?image
|
||||
:in $ $before-db %% ?ctx
|
||||
:where
|
||||
[(ground "%s") ?digest]
|
||||
[(adb/query (quote [:find
|
||||
(pull ?docker-image [:atomist/team-id
|
||||
:docker.image/digest
|
||||
:docker.image/tags
|
||||
:docker.image/created-at
|
||||
{:docker.image/file [:git.file/path]}
|
||||
{:docker.image/commit [:git.commit/sha
|
||||
{:git.commit/repo [:git.repo/name
|
||||
{:git.repo/org [:git.org/name]}]}]}
|
||||
{(:docker.tag/_image :as :docker.image/tag) [:docker.tag/name]}
|
||||
{(:docker.manifest-list/_images :as :docker.image/manifest-list) [:docker.manifest-list/digest
|
||||
{(:docker.tag/_manifest-list :as :docker.manifest-list/tag) [:docker.tag/name]}]}
|
||||
{(:vulnerability.report/_package :as :vulnerability.report/report) [:vulnerability.report/critical
|
||||
:vulnerability.report/high
|
||||
:vulnerability.report/medium
|
||||
:vulnerability.report/low
|
||||
:vulnerability.report/unspecified
|
||||
:vulnerability.report/total]}
|
||||
{:docker.image/repository [:docker.repository/host
|
||||
:docker.repository/badge
|
||||
:docker.repository/supported-tags
|
||||
(:docker.repository/repository :as :docker.repository/name)]}])
|
||||
:in $ $b %% ?ctx [?digest]
|
||||
:where
|
||||
(or-join [?digest ?docker-image]
|
||||
[?docker-image :docker.image/blob-digest ?digest]
|
||||
[?docker-image :docker.image/diff-chain-id ?digest])
|
||||
])
|
||||
?digest)
|
||||
?results]
|
||||
[(untuple ?results) [?result ...]]
|
||||
[(untuple ?result) [?image]]
|
||||
]
|
||||
21
query/enabled_skills.edn
Normal file
21
query/enabled_skills.edn
Normal file
@@ -0,0 +1,21 @@
|
||||
;; Copyright © 2022 Docker, Inc.
|
||||
;;
|
||||
;; Licensed under the Apache License, Version 2.0 (the "License");
|
||||
;; you may not use this file except in compliance with the License.
|
||||
;; You may obtain a copy of the License at
|
||||
;;
|
||||
;; http://www.apache.org/licenses/LICENSE-2.0
|
||||
;;
|
||||
;; Unless required by applicable law or agreed to in writing, software
|
||||
;; distributed under the License is distributed on an "AS IS" BASIS,
|
||||
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
;; See the License for the specific language governing permissions and
|
||||
;; limitations under the License.
|
||||
|
||||
[:find
|
||||
(pull ?skill [:atomist.skill/namespace :atomist.skill/name])
|
||||
:in $ $before-db % ?ctx
|
||||
:where
|
||||
[?skill :atomist.skill/id _]
|
||||
[?skill :atomist.skill/state :enabled]
|
||||
]
|
||||
164
query/query.go
Normal file
164
query/query.go
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright © 2022 Docker, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"olympos.io/encoding/edn"
|
||||
)
|
||||
|
||||
type ManifestList struct {
|
||||
Digest string `edn:"docker.manifest-list/digest"`
|
||||
Tags []struct {
|
||||
Name string `edn:"docker.tag/name"`
|
||||
} `edn:"docker.manifest-list/tag"`
|
||||
}
|
||||
|
||||
type Report struct {
|
||||
Critical int64 `edn:"vulnerability.report/critical"`
|
||||
High int64 `edn:"vulnerability.report/high"`
|
||||
Medium int64 `edn:"vulnerability.report/medium"`
|
||||
Low int64 `edn:"vulnerability.report/low"`
|
||||
Unspecified int64 `edn:"vulnerability.report/unspecified"`
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
TeamId string `edn:"atomist/team-id"`
|
||||
Digest string `edn:"docker.image/digest"`
|
||||
CreatedAt time.Time `edn:"docker.image/created-at"`
|
||||
Tags []string `edn:"docker.image/tags"`
|
||||
Tag []struct {
|
||||
Name string `edn:"docker.tag/name"`
|
||||
} `edn:"docker.image/tag"`
|
||||
ManifestList []ManifestList `edn:"docker.image/manifest-list"`
|
||||
Repository struct {
|
||||
Badge string `edn:"docker.repository/badge"`
|
||||
Host string `edn:"docker.repository/host"`
|
||||
Name string `edn:"docker.repository/name"`
|
||||
SupportedTags []string `edn:"docker.repository/supported-tags"`
|
||||
} `edn:"docker.image/repository"`
|
||||
File struct {
|
||||
Path string `edn:"git.file/path"`
|
||||
} `edn:"docker.image/file"`
|
||||
Commit struct {
|
||||
Sha string `edn:"git.commit/sha"`
|
||||
Repo struct {
|
||||
Name string `edn:"git.repo/name"`
|
||||
Org struct {
|
||||
Name string `edn:"git.org/name"`
|
||||
} `edn:"git.repo/org"`
|
||||
} `edn:"git.commit/repo"`
|
||||
} `edn:"docker.image/commit"`
|
||||
Report []Report `edn:"vulnerability.report/report"`
|
||||
}
|
||||
|
||||
type QueryResult struct {
|
||||
Query struct {
|
||||
Data [][]Image `edn:"data"`
|
||||
} `edn:"query"`
|
||||
}
|
||||
|
||||
//go:embed base_image_query.edn
|
||||
var baseImageQuery string
|
||||
|
||||
//go:embed enabled_skills.edn
|
||||
var enabledSkillsQuery string
|
||||
|
||||
func CheckAuth(workspace string, apiKey string) bool {
|
||||
resp, err := query(enabledSkillsQuery, workspace, apiKey)
|
||||
if resp.StatusCode != 200 || err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ForBaseImage returns images with matching digest in :docker.image/blob-digest or :docker.image/diff-chain-id
|
||||
func ForBaseImage(digest digest.Digest, workspace string, apiKey string) (*[]Image, error) {
|
||||
resp, err := query(fmt.Sprintf(baseImageQuery, digest), workspace, apiKey)
|
||||
|
||||
if workspace == "" || apiKey == "" {
|
||||
var result QueryResult
|
||||
err = edn.NewDecoder(resp.Body).Decode(&result)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal response")
|
||||
}
|
||||
if len(result.Query.Data) > 0 {
|
||||
return &result.Query.Data[0], nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
} else {
|
||||
var images [][]Image
|
||||
err = edn.NewDecoder(resp.Body).Decode(&images)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal response")
|
||||
}
|
||||
if len(images) > 0 {
|
||||
image := make([]Image, 0)
|
||||
|
||||
for _, img := range images {
|
||||
tba := true
|
||||
for j := range image {
|
||||
if image[j].Digest == img[0].Digest && img[0].TeamId == "A0GLG1QQA" {
|
||||
image[j] = img[0]
|
||||
tba = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if tba {
|
||||
image = append(image, img[0])
|
||||
}
|
||||
}
|
||||
|
||||
return &image, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func query(query string, workspace string, apiKey string) (*http.Response, error) {
|
||||
url := "https://api.atomist.com/datalog/team/" + workspace
|
||||
if workspace == "" || apiKey == "" {
|
||||
url = "https://api.atomist.com/datalog/shared-vulnerability/queries"
|
||||
query = fmt.Sprintf(`{:queries [{:name "query" :query %s}]}`, query)
|
||||
} else {
|
||||
query = fmt.Sprintf(`{:query %s}`, query)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(query))
|
||||
if apiKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/edn")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to create http client")
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to run query")
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
83
registry/registry.go
Normal file
83
registry/registry.go
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright © 2022 Docker, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1/daemon"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// DigestForImage retrieves the layer digests from either a local or remote image
|
||||
func DigestForImage(dockerCli command.Cli, image string) ([]digest.Digest, error) {
|
||||
digests := make([]digest.Digest, 0)
|
||||
|
||||
limg, _, err := dockerCli.Client().ImageInspectWithRaw(context.Background(), image)
|
||||
if err == nil {
|
||||
for _, l := range limg.RootFS.Layers {
|
||||
parsed, _ := digest.Parse(l)
|
||||
digests = append(digests, parsed)
|
||||
}
|
||||
return digests, nil
|
||||
}
|
||||
|
||||
ref, err := name.ParseReference(image)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse reference: %s", image)
|
||||
}
|
||||
|
||||
// check local daemon first
|
||||
img, err := daemon.Image(ref)
|
||||
if err != nil {
|
||||
// image doesn't exist in daemon; try remote
|
||||
index, _ := remote.Index(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
|
||||
if index != nil {
|
||||
m, _ := index.IndexManifest()
|
||||
for _, manifest := range m.Manifests {
|
||||
ref, _ = name.ParseReference(fmt.Sprintf("%s@%s", ref.Context(), manifest.Digest))
|
||||
if manifest.Platform.OS == "linux" && manifest.Platform.Architecture == "amd64" {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
desc, err := remote.Get(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to pull image: %s", image)
|
||||
} else if desc != nil {
|
||||
img, _ = desc.Image()
|
||||
}
|
||||
}
|
||||
|
||||
if img != nil {
|
||||
layers, _ := img.Layers()
|
||||
for _, layer := range layers {
|
||||
d, _ := layer.DiffID()
|
||||
parsed, _ := digest.Parse(d.String())
|
||||
digests = append(digests, parsed)
|
||||
}
|
||||
}
|
||||
|
||||
return digests, nil
|
||||
}
|
||||
Reference in New Issue
Block a user