Initial commit

Signed-off-by: Christian Dupuis <cd@atomist.com>
This commit is contained in:
Christian Dupuis
2022-09-16 13:01:12 +02:00
commit a6aaee0dbb
16 changed files with 2920 additions and 0 deletions

25
.github/workflows/go.yml vendored Normal file
View 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
View File

@@ -0,0 +1,3 @@
dist/
/base-cli-plugin
/go-skill.iml

29
.goreleaser.yaml Normal file
View 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
View 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
View 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.
![docker base detect](docs/screenshot.png "docker base detect")
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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

58
go.mod Normal file
View 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
)

1157
go.sum Normal file

File diff suppressed because it is too large Load Diff

725
install.sh Normal file
View 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
View 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
View 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,
})
}

View 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
View 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
View 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
View 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
}