mirror of
https://github.com/containers/skopeo.git
synced 2026-01-30 13:58:48 +00:00
Compare commits
420 Commits
v0.2.0
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
723f050465 | ||
|
|
05e6fa25ed | ||
|
|
a0f7bcde83 | ||
|
|
8b694b2649 | ||
|
|
069208bedc | ||
|
|
623d90afbb | ||
|
|
0583c8e641 | ||
|
|
76b7472044 | ||
|
|
aab0d6da59 | ||
|
|
e44bf704dd | ||
|
|
e02b7b21b3 | ||
|
|
2c19d55c3f | ||
|
|
f110fb7969 | ||
|
|
2091af3db1 | ||
|
|
32aa9fe647 | ||
|
|
ffee6f7219 | ||
|
|
3761ea6714 | ||
|
|
8e25f6bbeb | ||
|
|
59ceb6a5ea | ||
|
|
78cb493f95 | ||
|
|
c5dcc3b7d5 | ||
|
|
038f70e6f5 | ||
|
|
53b9ccff29 | ||
|
|
e9207c4564 | ||
|
|
f21d940c6e | ||
|
|
c35944bec0 | ||
|
|
266dc3dc9a | ||
|
|
91d9ccf5e5 | ||
|
|
4e57679c9a | ||
|
|
68f188ae77 | ||
|
|
0faf160170 | ||
|
|
69decaeb1d | ||
|
|
001775e994 | ||
|
|
fc448c2253 | ||
|
|
b10d3e43a4 | ||
|
|
a32be320cb | ||
|
|
5e13a55444 | ||
|
|
c0d259712c | ||
|
|
70abdf7334 | ||
|
|
f232ae499b | ||
|
|
aba84840dc | ||
|
|
e536c4da34 | ||
|
|
a1a8692457 | ||
|
|
5a594bff65 | ||
|
|
2eb35e7af9 | ||
|
|
00490a2cbb | ||
|
|
9a10ee2f1f | ||
|
|
002b2e4db9 | ||
|
|
891d9750a3 | ||
|
|
d6912022b5 | ||
|
|
eab7c4b0d1 | ||
|
|
7898ffaf23 | ||
|
|
ce4304a0ad | ||
|
|
610c612129 | ||
|
|
ad9f1d7bb9 | ||
|
|
37f15d6d11 | ||
|
|
2d3f3ed901 | ||
|
|
65d3890ea1 | ||
|
|
87f36844c3 | ||
|
|
a81cd74734 | ||
|
|
5a3e8b6150 | ||
|
|
88979a6a88 | ||
|
|
146af8cd59 | ||
|
|
6b95125757 | ||
|
|
6ee20f9d2a | ||
|
|
c84fc7d243 | ||
|
|
060fe4b47f | ||
|
|
3a759d5136 | ||
|
|
f15564f705 | ||
|
|
85e0fde20e | ||
|
|
fe1cd126f6 | ||
|
|
7a74faf4c1 | ||
|
|
dbe6764b35 | ||
|
|
5485daff13 | ||
|
|
cfbabac961 | ||
|
|
5907b4ef08 | ||
|
|
c456cef9bd | ||
|
|
0196219924 | ||
|
|
e945435dea | ||
|
|
c5103c6b51 | ||
|
|
61722a8a70 | ||
|
|
cc3ddf4804 | ||
|
|
d9f4377831 | ||
|
|
0717014e46 | ||
|
|
80dcddef36 | ||
|
|
6b41287cbf | ||
|
|
bef5e4505e | ||
|
|
f5a028e4d9 | ||
|
|
3d1d2978d7 | ||
|
|
035eb33f1f | ||
|
|
6cbb0c4c88 | ||
|
|
663fe44f27 | ||
|
|
cc24482985 | ||
|
|
b7bf15bc8b | ||
|
|
61b62f9e93 | ||
|
|
2c8655e251 | ||
|
|
94d588c480 | ||
|
|
a85e3beccf | ||
|
|
3878a37660 | ||
|
|
be600975a9 | ||
|
|
15f0d5cd2f | ||
|
|
6fa634227c | ||
|
|
e224b78efc | ||
|
|
1c4b0fc33d | ||
|
|
81e66ffc46 | ||
|
|
5995ceedf9 | ||
|
|
7a9d638989 | ||
|
|
40f5a8cf69 | ||
|
|
a6e50d32d2 | ||
|
|
9a88c3986d | ||
|
|
ac5241482c | ||
|
|
aff1b6215b | ||
|
|
e0ba05af59 | ||
|
|
55b9782058 | ||
|
|
4ab7faa800 | ||
|
|
c51c7b4e4d | ||
|
|
3375a905cc | ||
|
|
f3c8d26cd8 | ||
|
|
e1dc30b6e1 | ||
|
|
a9e9bdc534 | ||
|
|
6c8b8c20f5 | ||
|
|
0e1ee196bd | ||
|
|
77a2e08eb2 | ||
|
|
a3c21f25c9 | ||
|
|
1e1952693a | ||
|
|
efc0170ee8 | ||
|
|
0d0a97eb00 | ||
|
|
47a6716921 | ||
|
|
18e6c6f17b | ||
|
|
ef6f46a3b5 | ||
|
|
31562124a3 | ||
|
|
b544c1be3a | ||
|
|
0c0a17b641 | ||
|
|
2e90a8af5a | ||
|
|
2294113c78 | ||
|
|
bdb117ded6 | ||
|
|
beadcbb17d | ||
|
|
fe57e80c18 | ||
|
|
ac07bf278a | ||
|
|
3c33cb4556 | ||
|
|
f94d85aa8e | ||
|
|
b0da05656d | ||
|
|
9828f21007 | ||
|
|
6ee4b2dc84 | ||
|
|
b3a15e7288 | ||
|
|
f771cb0d39 | ||
|
|
c4fb93647a | ||
|
|
81535c5244 | ||
|
|
7442052875 | ||
|
|
84232cf306 | ||
|
|
c339a1abe9 | ||
|
|
766927d1d4 | ||
|
|
fc78c93ad2 | ||
|
|
4987a67293 | ||
|
|
131b2b8c63 | ||
|
|
342b8398e2 | ||
|
|
6b260e1686 | ||
|
|
6294875a04 | ||
|
|
8cc9fcae6f | ||
|
|
4769dd0689 | ||
|
|
0fb1121f36 | ||
|
|
4aaa9b401d | ||
|
|
44087c4866 | ||
|
|
f36f7dbfdf | ||
|
|
07c0e6a50f | ||
|
|
ed321809d3 | ||
|
|
13ef91744c | ||
|
|
5b8fe7ffa5 | ||
|
|
8cd57ef8de | ||
|
|
1b813f805b | ||
|
|
f3a8a7360d | ||
|
|
42e9121eba | ||
|
|
4597c09522 | ||
|
|
2ec251c2e2 | ||
|
|
e717a59174 | ||
|
|
c88576b2fc | ||
|
|
901f7e9c47 | ||
|
|
0f4dc80c99 | ||
|
|
353f3a23e1 | ||
|
|
4b4ad6285e | ||
|
|
6b007c70c7 | ||
|
|
6a48870594 | ||
|
|
5d73dea577 | ||
|
|
82e461ff9d | ||
|
|
e30abff31b | ||
|
|
7fee9122fb | ||
|
|
2342171cdf | ||
|
|
58c9eccffd | ||
|
|
23fa1666dd | ||
|
|
fa2e385713 | ||
|
|
958c361c97 | ||
|
|
72e8af59aa | ||
|
|
873fbee01b | ||
|
|
1a3eb478a7 | ||
|
|
bc0ecfc8f6 | ||
|
|
4ad2c75b52 | ||
|
|
9662633059 | ||
|
|
18fe2fd00a | ||
|
|
19f9a6adc2 | ||
|
|
11b4fd3956 | ||
|
|
8d2c20f160 | ||
|
|
6d7d0e7d39 | ||
|
|
39f8117c27 | ||
|
|
e709329b03 | ||
|
|
1a3ae1411e | ||
|
|
35daba1194 | ||
|
|
65c5b0bf8d | ||
|
|
cd884fa529 | ||
|
|
3a72464068 | ||
|
|
32e242586c | ||
|
|
a7f4b26f90 | ||
|
|
9bcae7060a | ||
|
|
98fdb042a1 | ||
|
|
ceaee440a6 | ||
|
|
6eb4fb64a0 | ||
|
|
a75daba386 | ||
|
|
67d72d27c9 | ||
|
|
362f70b056 | ||
|
|
035e25496a | ||
|
|
10da9f7012 | ||
|
|
c18a977e96 | ||
|
|
0954077fd7 | ||
|
|
bde39ce91d | ||
|
|
a422316d48 | ||
|
|
21aa04e3c3 | ||
|
|
4cc72b9f69 | ||
|
|
50ff352e41 | ||
|
|
027d7e466a | ||
|
|
69f51ac183 | ||
|
|
d8bc8b62e9 | ||
|
|
f9773889a1 | ||
|
|
6dabefa9db | ||
|
|
5364f84119 | ||
|
|
4ba7d50174 | ||
|
|
12729c4d7e | ||
|
|
44beab63c9 | ||
|
|
669627d1b6 | ||
|
|
1c45df1e03 | ||
|
|
f91a9c569d | ||
|
|
248a1dd01a | ||
|
|
3a75b51b59 | ||
|
|
2b4097bc13 | ||
|
|
8151b89b81 | ||
|
|
cbd7fb7d37 | ||
|
|
77293ff9c4 | ||
|
|
467b462b79 | ||
|
|
242b573f9a | ||
|
|
2d5f12b9a6 | ||
|
|
3c73c0c0cd | ||
|
|
ec17cfcbf1 | ||
|
|
1d0b1671f8 | ||
|
|
bbd800f974 | ||
|
|
12ab19f5fd | ||
|
|
05d172a1f5 | ||
|
|
45a9efb37f | ||
|
|
62bafb102d | ||
|
|
4eda1d092d | ||
|
|
5dd09d76c3 | ||
|
|
23cb1b7f19 | ||
|
|
c1f984a176 | ||
|
|
662f9ac8f7 | ||
|
|
ae26454014 | ||
|
|
5e1d64825c | ||
|
|
8767e73fe9 | ||
|
|
071462199d | ||
|
|
3bb23e355e | ||
|
|
c4998ebf3f | ||
|
|
a13b581760 | ||
|
|
c8c8d5db78 | ||
|
|
ad3d4aecbb | ||
|
|
87484a1754 | ||
|
|
58b9ec9e08 | ||
|
|
6911642122 | ||
|
|
3ede91cca6 | ||
|
|
5d5756cc83 | ||
|
|
5ad62b9415 | ||
|
|
88c8c47ce0 | ||
|
|
e4f656616c | ||
|
|
b05933fbc4 | ||
|
|
e5f549099b | ||
|
|
ea10e61f7d | ||
|
|
915f40d12a | ||
|
|
0c2c7f4016 | ||
|
|
135ce43169 | ||
|
|
0f94dbcdb3 | ||
|
|
f30bab47e6 | ||
|
|
baeaad61d9 | ||
|
|
c750be0107 | ||
|
|
84d051fc01 | ||
|
|
56f8222e12 | ||
|
|
78d2f67016 | ||
|
|
c24363ccda | ||
|
|
c052ed7ec8 | ||
|
|
5e88eb5761 | ||
|
|
4fb724fb7b | ||
|
|
e23b780072 | ||
|
|
d9058b3021 | ||
|
|
62fd5a76e1 | ||
|
|
6252c22112 | ||
|
|
26e6db1cc7 | ||
|
|
b7cdcb00ac | ||
|
|
153f18dc0a | ||
|
|
4012d0e30c | ||
|
|
494d237789 | ||
|
|
84c53d104a | ||
|
|
89fb89a456 | ||
|
|
960b610ff6 | ||
|
|
29eec32795 | ||
|
|
2fa7b998ba | ||
|
|
ebc438266d | ||
|
|
8f5eb45ba6 | ||
|
|
6284ceb2b6 | ||
|
|
5e2264d2b5 | ||
|
|
6e295a2097 | ||
|
|
19f9a5c2fa | ||
|
|
f63685f3c8 | ||
|
|
dc5f68fe5f | ||
|
|
0858cafffc | ||
|
|
2e343342d5 | ||
|
|
840c48752e | ||
|
|
0382b01687 | ||
|
|
ee72e803ec | ||
|
|
142142c040 | ||
|
|
6182aa30b1 | ||
|
|
ec9f8acf00 | ||
|
|
52b3a5bacc | ||
|
|
ac6b871f66 | ||
|
|
b17fb08f8b | ||
|
|
dd2e70e9b7 | ||
|
|
ba8cbf589b | ||
|
|
91dc0f3f4c | ||
|
|
7815c8ac6f | ||
|
|
233e61cf9a | ||
|
|
0e2611d3a6 | ||
|
|
96bd4a0619 | ||
|
|
6b78619cd1 | ||
|
|
0f458eec76 | ||
|
|
6b960ec031 | ||
|
|
fdc58131f8 | ||
|
|
63085f5bef | ||
|
|
091f9248dc | ||
|
|
dd7dd75334 | ||
|
|
b70dfae2ae | ||
|
|
0bd78a0604 | ||
|
|
9e0839c33f | ||
|
|
9bafa7e80d | ||
|
|
827293a13b | ||
|
|
6198daeb2c | ||
|
|
161ef5a224 | ||
|
|
9e99ad99d4 | ||
|
|
c36502ce31 | ||
|
|
f9b0d93ee0 | ||
|
|
4eaaf31249 | ||
|
|
c6b488a82c | ||
|
|
7cfc62922f | ||
|
|
5284f6d832 | ||
|
|
ae97c667e3 | ||
|
|
a2c1d46302 | ||
|
|
8b4b954332 | ||
|
|
c103d65284 | ||
|
|
c5183d0e34 | ||
|
|
16b435257b | ||
|
|
35f3595d02 | ||
|
|
0ee81dc9fe | ||
|
|
805885091f | ||
|
|
97ec6873fa | ||
|
|
d16cd39939 | ||
|
|
7439f94e22 | ||
|
|
443380731e | ||
|
|
56c6325ba0 | ||
|
|
0ae9db5dd6 | ||
|
|
677c29bf24 | ||
|
|
72376c4144 | ||
|
|
322625eeca | ||
|
|
9c1936fd07 | ||
|
|
3a94432e42 | ||
|
|
ce1f807aa0 | ||
|
|
a51af64dd9 | ||
|
|
a31d6069dc | ||
|
|
96353f2b64 | ||
|
|
2330455c8d | ||
|
|
91a88de6a1 | ||
|
|
2afe7a3e1e | ||
|
|
bec7f6977e | ||
|
|
60ecaffbe8 | ||
|
|
dcaee948d3 | ||
|
|
2fe7087d52 | ||
|
|
bd162028cd | ||
|
|
a214a305fd | ||
|
|
5093d5b5f6 | ||
|
|
0d9939dcd4 | ||
|
|
1b2de8ec5d | ||
|
|
ab2300500a | ||
|
|
fbf061260c | ||
|
|
4244d68240 | ||
|
|
dda31b3d4b | ||
|
|
2af172653c | ||
|
|
3247c0d229 | ||
|
|
eb024319de | ||
|
|
4ca9b139bb | ||
|
|
b79a37ead9 | ||
|
|
0ec2610f04 | ||
|
|
71a14d7df6 | ||
|
|
8936e76316 | ||
|
|
e21d6b3687 | ||
|
|
a6ab2291ba | ||
|
|
8f845aac23 | ||
|
|
439ea83081 | ||
|
|
42f68c1c76 | ||
|
|
8d252f82fd | ||
|
|
1ddb736b5a | ||
|
|
46fbbbd282 | ||
|
|
e7a7f018bd | ||
|
|
311fc89548 | ||
|
|
a6abdb8547 | ||
|
|
02407d98a5 | ||
|
|
b230a507e7 | ||
|
|
116add9d00 | ||
|
|
2415f3fa4d | ||
|
|
5f8d3fc639 |
150
.cirrus.yml
Normal file
150
.cirrus.yml
Normal file
@@ -0,0 +1,150 @@
|
||||
---
|
||||
|
||||
# Main collection of env. vars to set for all tasks and scripts.
|
||||
env:
|
||||
####
|
||||
#### Global variables used for all tasks
|
||||
####
|
||||
# Name of the ultimate destination branch for this CI run, PR or post-merge.
|
||||
DEST_BRANCH: "main"
|
||||
# Overrides default location (/tmp/cirrus) for repo clone
|
||||
GOPATH: &gopath "/var/tmp/go"
|
||||
GOBIN: "${GOPATH}/bin"
|
||||
GOCACHE: "${GOPATH}/cache"
|
||||
GOSRC: &gosrc "/var/tmp/go/src/github.com/containers/skopeo"
|
||||
# Required for consistency with containers/image CI
|
||||
SKOPEO_PATH: *gosrc
|
||||
CIRRUS_WORKING_DIR: *gosrc
|
||||
# The default is 'sh' if unspecified
|
||||
CIRRUS_SHELL: "/bin/bash"
|
||||
# Save a little typing (path relative to $CIRRUS_WORKING_DIR)
|
||||
SCRIPT_BASE: "./contrib/cirrus"
|
||||
|
||||
####
|
||||
#### Cache-image names to test with (double-quotes around names are critical)
|
||||
####
|
||||
FEDORA_NAME: "fedora-34"
|
||||
PRIOR_FEDORA_NAME: "fedora-33"
|
||||
UBUNTU_NAME: "ubuntu-2104"
|
||||
PRIOR_UBUNTU_NAME: "ubuntu-2010"
|
||||
|
||||
# Google-cloud VM Images
|
||||
IMAGE_SUFFIX: "c6032583541653504"
|
||||
FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}"
|
||||
PRIOR_FEDORA_CACHE_IMAGE_NAME: "prior-fedora-${IMAGE_SUFFIX}"
|
||||
UBUNTU_CACHE_IMAGE_NAME: "ubuntu-${IMAGE_SUFFIX}"
|
||||
PRIOR_UBUNTU_CACHE_IMAGE_NAME: "prior-ubuntu-${IMAGE_SUFFIX}"
|
||||
|
||||
# Container FQIN's
|
||||
FEDORA_CONTAINER_FQIN: "quay.io/libpod/fedora_podman:${IMAGE_SUFFIX}"
|
||||
PRIOR_FEDORA_CONTAINER_FQIN: "quay.io/libpod/prior-fedora_podman:${IMAGE_SUFFIX}"
|
||||
UBUNTU_CONTAINER_FQIN: "quay.io/libpod/ubuntu_podman:${IMAGE_SUFFIX}"
|
||||
PRIOR_UBUNTU_CONTAINER_FQIN: "quay.io/libpod/prior-ubuntu_podman:${IMAGE_SUFFIX}"
|
||||
|
||||
# Equivilent to image produced by 'make build-container'
|
||||
SKOPEO_CI_CONTAINER_FQIN: "quay.io/skopeo/ci:${DEST_BRANCH}"
|
||||
|
||||
|
||||
# Default timeout for each task
|
||||
timeout_in: 45m
|
||||
|
||||
|
||||
gcp_credentials: ENCRYPTED[52d9e807b531b37ab14e958cb5a72499460663f04c8d73e22ad608c027a31118420f1c80f0be0882fbdf96f49d8f9ac0]
|
||||
|
||||
|
||||
validate_task:
|
||||
# The git-validation tool doesn't work well on branch or tag push,
|
||||
# under Cirrus-CI, due to challenges obtaining the starting commit ID.
|
||||
# Only do validation for PRs.
|
||||
only_if: $CIRRUS_PR != ''
|
||||
container: &build_container
|
||||
image: "${SKOPEO_CI_CONTAINER_FQIN}"
|
||||
cpu: 4
|
||||
memory: 8
|
||||
script: make validate-local
|
||||
|
||||
|
||||
#####
|
||||
##### NOTE: This task is subtantially duplicated in the containers/image
|
||||
##### repository's `.cirrus.yml`. Changes made here should be fully merged
|
||||
##### prior to being manually duplicated and maintained in containers/image.
|
||||
#####
|
||||
test_skopeo_task:
|
||||
alias: test_skopeo
|
||||
depends_on:
|
||||
- validate
|
||||
gce_instance:
|
||||
image_project: libpod-218412
|
||||
zone: "us-central1-f"
|
||||
cpu: 2
|
||||
memory: "4Gb"
|
||||
# Required to be 200gig, do not modify - has i/o performance impact
|
||||
# according to gcloud CLI tool warning messages.
|
||||
disk: 200
|
||||
image_name: ${FEDORA_CACHE_IMAGE_NAME}
|
||||
matrix:
|
||||
- name: "Skopeo Test"
|
||||
env:
|
||||
BUILDTAGS: 'btrfs_noversion libdm_no_deferred_remove'
|
||||
- name: "Skopeo Test w/ opengpg"
|
||||
env:
|
||||
BUILDTAGS: 'btrfs_noversion libdm_no_deferred_remove containers_image_openpgp'
|
||||
setup_script: >-
|
||||
"${GOSRC}/${SCRIPT_BASE}/runner.sh" setup
|
||||
vendor_script: >-
|
||||
"${SKOPEO_PATH}/${SCRIPT_BASE}/runner.sh" vendor
|
||||
build_script: >-
|
||||
"${SKOPEO_PATH}/${SCRIPT_BASE}/runner.sh" build
|
||||
validate_script: >-
|
||||
"${SKOPEO_PATH}/${SCRIPT_BASE}/runner.sh" validate
|
||||
unit_script: >-
|
||||
"${SKOPEO_PATH}/${SCRIPT_BASE}/runner.sh" unit
|
||||
integration_script: >-
|
||||
"${SKOPEO_PATH}/${SCRIPT_BASE}/runner.sh" integration
|
||||
system_script: >
|
||||
"${SKOPEO_PATH}/${SCRIPT_BASE}/runner.sh" system
|
||||
|
||||
|
||||
# This task is critical. It updates the "last-used by" timestamp stored
|
||||
# in metadata for all VM images. This mechanism functions in tandem with
|
||||
# an out-of-band pruning operation to remove disused VM images.
|
||||
meta_task:
|
||||
name: "VM img. keepalive"
|
||||
alias: meta
|
||||
container: &smallcontainer
|
||||
cpu: 2
|
||||
memory: 2
|
||||
image: quay.io/libpod/imgts:latest
|
||||
env:
|
||||
# Space-separated list of images used by this repository state
|
||||
IMGNAMES: >-
|
||||
${FEDORA_CACHE_IMAGE_NAME}
|
||||
${PRIOR_FEDORA_CACHE_IMAGE_NAME}
|
||||
${UBUNTU_CACHE_IMAGE_NAME}
|
||||
${PRIOR_UBUNTU_CACHE_IMAGE_NAME}
|
||||
BUILDID: "${CIRRUS_BUILD_ID}"
|
||||
REPOREF: "${CIRRUS_REPO_NAME}"
|
||||
GCPJSON: ENCRYPTED[6867b5a83e960e7c159a98fe6c8360064567a071c6f4b5e7d532283ecd870aa65c94ccd74bdaa9bf7aadac9d42e20a67]
|
||||
GCPNAME: ENCRYPTED[1cf558ae125e3c39ec401e443ad76452b25d790c45eb73d77c83eb059a0f7fd5085ef7e2f7e410b04ea6e83b0aab2eb1]
|
||||
GCPPROJECT: libpod-218412
|
||||
clone_script: &noop mkdir -p "$CIRRUS_WORKING_DIR"
|
||||
script: /usr/local/bin/entrypoint.sh
|
||||
|
||||
|
||||
# Status aggregator for all tests. This task simply ensures a defined
|
||||
# set of tasks all passed, and allows confirming that based on the status
|
||||
# of this task.
|
||||
success_task:
|
||||
name: "Total Success"
|
||||
alias: success
|
||||
# N/B: ALL tasks must be listed here, minus their '_task' suffix.
|
||||
depends_on:
|
||||
- validate
|
||||
- test_skopeo
|
||||
- meta
|
||||
container: *smallcontainer
|
||||
env:
|
||||
CTR_FQIN: ${FEDORA_CONTAINER_FQIN}
|
||||
TEST_ENVIRON: container
|
||||
clone_script: *noop
|
||||
script: /bin/true
|
||||
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "10:00"
|
||||
timezone: Europe/Berlin
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
25
.github/workflow/stale.yml
vendored
Normal file
25
.github/workflow/stale.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Mark stale issues and pull requests
|
||||
|
||||
# Please refer to https://github.com/actions/stale/blob/master/action.yml
|
||||
# to see all config knobs of the stale action.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'A friendly reminder that this issue had no activity for 30 days.'
|
||||
stale-pr-message: 'A friendly reminder that this PR had no activity for 30 days.'
|
||||
stale-issue-label: 'stale-issue'
|
||||
stale-pr-label: 'stale-pr'
|
||||
days-before-stale: 30
|
||||
days-before-close: 365
|
||||
remove-stale-when-updated: true
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,6 +1,10 @@
|
||||
*.1
|
||||
/layers-*
|
||||
/skopeo
|
||||
result
|
||||
|
||||
# ignore JetBrains IDEs (GoLand) config folder
|
||||
.idea
|
||||
.idea
|
||||
|
||||
# Ignore the bin directory
|
||||
bin
|
||||
|
||||
87
.travis.yml
87
.travis.yml
@@ -1,28 +1,75 @@
|
||||
language: go
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
sudo: required
|
||||
services:
|
||||
- docker
|
||||
- os: osx
|
||||
|
||||
go:
|
||||
- 1.13.x
|
||||
- 1.15.x
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
install:
|
||||
# NOTE: The (brew update) should not be necessary, and slows things down;
|
||||
# we include it as a workaround for https://github.com/Homebrew/brew/issues/3299
|
||||
# ideally Travis should bake the (brew update) into its images
|
||||
# (https://github.com/travis-ci/travis-ci/issues/8552 ), but that’s only going
|
||||
# to happen around November 2017 per https://blog.travis-ci.com/2017-10-16-a-new-default-os-x-image-is-coming .
|
||||
# Remove the (brew update) at that time.
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update && brew install gpgme ; fi
|
||||
env:
|
||||
global:
|
||||
# Multiarch manifest will support architectures from this list. It should be the same architectures, as ones in image-build-push step in this Travis config
|
||||
- MULTIARCH_MANIFEST_ARCHITECTURES="amd64 s390x ppc64le"
|
||||
# env variables for stable image build
|
||||
- STABLE_IMAGE=quay.io/skopeo/stable:v1.2.0
|
||||
- EXTRA_STABLE_IMAGE=quay.io/containers/skopeo:v1.2.0
|
||||
# env variable for upstream image build
|
||||
- UPSTREAM_IMAGE=quay.io/skopeo/upstream:master
|
||||
|
||||
script:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then hack/travis_osx.sh ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make vendor && ./hack/tree_status.sh && make check ; fi
|
||||
# Just declaration of the image-build-push step with script actions to execute
|
||||
x_base_steps:
|
||||
- &image-build-push
|
||||
services:
|
||||
- docker
|
||||
os: linux
|
||||
dist: focal
|
||||
script:
|
||||
# skopeo upstream image build
|
||||
- make -f release/Makefile build-image/upstream
|
||||
# Push image in case if build is started via cron job or code is pushed to master
|
||||
- if [ "$TRAVIS_EVENT_TYPE" == "push" ] || [ "$TRAVIS_EVENT_TYPE" == "cron" ]; then
|
||||
make -f release/Makefile push-image/upstream ;
|
||||
fi
|
||||
|
||||
# skopeo stable image build
|
||||
- make -f release/Makefile build-image/stable
|
||||
# Push image in case if build is started via cron job or code is pushed to master
|
||||
- if [ "$TRAVIS_EVENT_TYPE" == "push" ] || [ "$TRAVIS_EVENT_TYPE" == "cron" ]; then
|
||||
make -f release/Makefile push-image/stable ;
|
||||
fi
|
||||
|
||||
# Just declaration of stage order to run
|
||||
stages:
|
||||
# Build and push image for 1 architecture
|
||||
- name: image-build-push
|
||||
if: branch = master
|
||||
# Create and push image manifest to have multiarch image on top of architecture specific images
|
||||
- name: manifest-multiarch-push
|
||||
if: (type IN (push, cron)) AND branch = master
|
||||
|
||||
# Actual execution of steps
|
||||
jobs:
|
||||
include:
|
||||
# Run 3 image-build-push tasks in parallel for linux/amd64, linux/s390x and linux/ppc64le platforms (for upstream and stable)
|
||||
- stage: image-build-push
|
||||
<<: *image-build-push
|
||||
name: images for amd64
|
||||
arch: amd64
|
||||
|
||||
- stage: image-build-push
|
||||
<<: *image-build-push
|
||||
name: images for s390x
|
||||
arch: s390x
|
||||
|
||||
- stage: image-build-push
|
||||
<<: *image-build-push
|
||||
name: images for ppc64le
|
||||
arch: ppc64le
|
||||
|
||||
# Run final task to generate multi-arch image manifests (for upstream and stable)
|
||||
- stage: manifest-multiarch-push
|
||||
os: linux
|
||||
dist: focal
|
||||
script:
|
||||
- make -f release/Makefile push-manifest-multiarch/upstream
|
||||
- make -f release/Makefile push-manifest-multiarch/stable
|
||||
|
||||
@@ -134,7 +134,7 @@ When new PRs for [containers/image](https://github.com/containers/image) break `
|
||||
- create out a new branch in your `skopeo` checkout and switch to it
|
||||
- update `vendor.conf`. Find out the `containers/image` dependency; update it to vendor from your own branch and your own repository fork (e.g. `github.com/containers/image my-branch https://github.com/runcom/image`)
|
||||
- run `make vendor`
|
||||
- make any other necessary changes in the skopeo repo (e.g. add other dependencies now requied by `containers/image`, or update skopeo for changed `containers/image` API)
|
||||
- make any other necessary changes in the skopeo repo (e.g. add other dependencies now required by `containers/image`, or update skopeo for changed `containers/image` API)
|
||||
- optionally add new integration tests to the skopeo repo
|
||||
- submit the resulting branch as a skopeo PR, marked “DO NOT MERGE”
|
||||
- iterate until tests pass and the PR is reviewed
|
||||
@@ -142,7 +142,7 @@ When new PRs for [containers/image](https://github.com/containers/image) break `
|
||||
- as soon as possible after that, in the skopeo PR, restore the `containers/image` line in `vendor.conf` to use `containers/image:master`
|
||||
- run `make vendor`
|
||||
- update the skopeo PR with the result, drop the “DO NOT MERGE” marking
|
||||
- after tests complete succcesfully again, merge the skopeo PR
|
||||
- after tests complete successfully again, merge the skopeo PR
|
||||
|
||||
## Communications
|
||||
|
||||
|
||||
@@ -7,10 +7,13 @@ RUN dnf -y update && dnf install -y make git golang golang-github-cpuguy83-md2ma
|
||||
# gpgme bindings deps
|
||||
libassuan-devel gpgme-devel \
|
||||
gnupg \
|
||||
# htpasswd for system tests
|
||||
httpd-tools \
|
||||
# OpenShift deps
|
||||
which tar wget hostname util-linux bsdtar socat ethtool device-mapper iptables tree findutils nmap-ncat e2fsprogs xfsprogs lsof docker iproute \
|
||||
bats jq podman runc \
|
||||
golint \
|
||||
openssl \
|
||||
&& dnf clean all
|
||||
|
||||
# Install two versions of the registry. The first is an older version that
|
||||
@@ -18,6 +21,7 @@ RUN dnf -y update && dnf install -y make git golang golang-github-cpuguy83-md2ma
|
||||
# both. This allows integration-cli tests to cover push/pull with both schema1
|
||||
# and schema2 manifests.
|
||||
RUN set -x \
|
||||
&& export GO111MODULE=off \
|
||||
&& REGISTRY_COMMIT_SCHEMA1=ec87e9b6971d831f0eff752ddb54fb64693e51cd \
|
||||
&& REGISTRY_COMMIT=47a064d4195a9b56133891bbb13620c3ac83a827 \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
@@ -31,6 +35,7 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
RUN set -x \
|
||||
&& export GO111MODULE=off \
|
||||
&& export GOPATH=$(mktemp -d) \
|
||||
&& git clone --depth 1 -b v1.5.0-alpha.3 git://github.com/openshift/origin "$GOPATH/src/github.com/openshift/origin" \
|
||||
# The sed edits out a "go < 1.5" check which works incorrectly with go ≥ 1.10. \
|
||||
@@ -43,6 +48,7 @@ RUN set -x \
|
||||
|
||||
ENV GOPATH /usr/share/gocode:/go
|
||||
ENV PATH $GOPATH/bin:/usr/share/gocode/bin:$PATH
|
||||
ENV container_magic 85531765-346b-4316-bdb8-358e4cca9e5d
|
||||
RUN go version
|
||||
WORKDIR /go/src/github.com/containers/skopeo
|
||||
COPY . /go/src/github.com/containers/skopeo
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
FROM ubuntu:19.10
|
||||
FROM registry.fedoraproject.org/fedora:33
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
golang \
|
||||
libbtrfs-dev \
|
||||
git-core \
|
||||
libdevmapper-dev \
|
||||
libgpgme11-dev \
|
||||
go-md2man \
|
||||
libglib2.0-dev
|
||||
RUN dnf update -y && \
|
||||
dnf install -y \
|
||||
btrfs-progs-devel \
|
||||
device-mapper-devel \
|
||||
golang \
|
||||
gpgme-devel \
|
||||
make
|
||||
|
||||
ENV GOPATH=/
|
||||
WORKDIR /src/github.com/containers/skopeo
|
||||
|
||||
120
Makefile
120
Makefile
@@ -2,33 +2,37 @@
|
||||
|
||||
export GOPROXY=https://proxy.golang.org
|
||||
|
||||
ifeq ($(shell uname),Darwin)
|
||||
PREFIX ?= ${DESTDIR}/usr/local
|
||||
DARWIN_BUILD_TAG=
|
||||
# On macOS, (brew install gpgme) installs it within /usr/local, but /usr/local/include is not in the default search path.
|
||||
# Rather than hard-code this directory, use gpgme-config. Sadly that must be done at the top-level user
|
||||
# instead of locally in the gpgme subpackage, because cgo supports only pkg-config, not general shell scripts,
|
||||
# and gpgme does not install a pkg-config file.
|
||||
# On some plaforms (eg. macOS, FreeBSD) gpgme is installed in /usr/local/ but /usr/local/include/ is
|
||||
# not in the default search path. Rather than hard-code this directory, use gpgme-config.
|
||||
# Sadly that must be done at the top-level user instead of locally in the gpgme subpackage, because cgo
|
||||
# supports only pkg-config, not general shell scripts, and gpgme does not install a pkg-config file.
|
||||
# If gpgme is not installed or gpgme-config can’t be found for other reasons, the error is silently ignored
|
||||
# (and the user will probably find out because the cgo compilation will fail).
|
||||
GPGME_ENV := CGO_CFLAGS="$(shell gpgme-config --cflags 2>/dev/null)" CGO_LDFLAGS="$(shell gpgme-config --libs 2>/dev/null)"
|
||||
else
|
||||
PREFIX ?= ${DESTDIR}/usr
|
||||
endif
|
||||
|
||||
INSTALLDIR=${PREFIX}/bin
|
||||
MANINSTALLDIR=${PREFIX}/share/man
|
||||
CONTAINERSSYSCONFIGDIR=${DESTDIR}/etc/containers
|
||||
REGISTRIESDDIR=${CONTAINERSSYSCONFIGDIR}/registries.d
|
||||
SIGSTOREDIR=${DESTDIR}/var/lib/containers/sigstore
|
||||
BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions
|
||||
# Normally empty, DESTDIR can be used to relocate the entire install-tree
|
||||
DESTDIR ?=
|
||||
CONTAINERSCONFDIR ?= ${DESTDIR}/etc/containers
|
||||
REGISTRIESDDIR ?= ${CONTAINERSCONFDIR}/registries.d
|
||||
SIGSTOREDIR ?= ${DESTDIR}/var/lib/containers/sigstore
|
||||
PREFIX ?= ${DESTDIR}/usr/local
|
||||
BINDIR ?= ${PREFIX}/bin
|
||||
MANDIR ?= ${PREFIX}/share/man
|
||||
BASHCOMPLETIONSDIR ?= ${PREFIX}/share/bash-completion/completions
|
||||
|
||||
GO ?= go
|
||||
GOBIN := $(shell $(GO) env GOBIN)
|
||||
GOOS ?= $(shell go env GOOS)
|
||||
GOARCH ?= $(shell go env GOARCH)
|
||||
|
||||
ifeq ($(GOBIN),)
|
||||
GOBIN := $(GOPATH)/bin
|
||||
endif
|
||||
|
||||
# Required for integration-tests to detect they are running inside a specific
|
||||
# container image. Env. var defined in image, make does not automatically
|
||||
# pass to children unless explicitly exported
|
||||
export container_magic
|
||||
CONTAINER_RUNTIME := $(shell command -v podman 2> /dev/null || echo docker)
|
||||
GOMD2MAN ?= $(shell command -v go-md2man || echo '$(GOBIN)/go-md2man')
|
||||
|
||||
@@ -43,8 +47,10 @@ ifeq ($(DEBUG), 1)
|
||||
override GOGCFLAGS += -N -l
|
||||
endif
|
||||
|
||||
ifeq ($(shell $(GO) env GOOS), linux)
|
||||
GO_DYN_FLAGS="-buildmode=pie"
|
||||
ifeq ($(GOOS), linux)
|
||||
ifneq ($(GOARCH),$(filter $(GOARCH),mips mipsle mips64 mips64le ppc64 riscv64))
|
||||
GO_DYN_FLAGS="-buildmode=pie"
|
||||
endif
|
||||
endif
|
||||
|
||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||
@@ -62,12 +68,15 @@ CONTAINER_RUN := $(CONTAINER_CMD) "$(IMAGE)"
|
||||
|
||||
GIT_COMMIT := $(shell git rev-parse HEAD 2> /dev/null || true)
|
||||
|
||||
EXTRA_LDFLAGS ?=
|
||||
SKOPEO_LDFLAGS := -ldflags '-X main.gitCommit=${GIT_COMMIT} $(EXTRA_LDFLAGS)'
|
||||
|
||||
MANPAGES_MD = $(wildcard docs/*.md)
|
||||
MANPAGES ?= $(MANPAGES_MD:%.md=%)
|
||||
|
||||
BTRFS_BUILD_TAG = $(shell hack/btrfs_tag.sh) $(shell hack/btrfs_installed_tag.sh)
|
||||
LIBDM_BUILD_TAG = $(shell hack/libdm_tag.sh)
|
||||
LOCAL_BUILD_TAGS = $(BTRFS_BUILD_TAG) $(LIBDM_BUILD_TAG) $(DARWIN_BUILD_TAG)
|
||||
LOCAL_BUILD_TAGS = $(BTRFS_BUILD_TAG) $(LIBDM_BUILD_TAG)
|
||||
BUILDTAGS += $(LOCAL_BUILD_TAGS)
|
||||
|
||||
ifeq ($(DISABLE_CGO), 1)
|
||||
@@ -78,14 +87,17 @@ endif
|
||||
# Note: Uses the -N -l go compiler options to disable compiler optimizations
|
||||
# and inlining. Using these build options allows you to subsequently
|
||||
# use source debugging tools like delve.
|
||||
all: binary docs-in-container
|
||||
all: bin/skopeo docs
|
||||
|
||||
help:
|
||||
@echo "Usage: make <target>"
|
||||
@echo
|
||||
@echo "Defaults to building bin/skopeo and docs"
|
||||
@echo
|
||||
@echo " * 'install' - Install binaries and documents to system locations"
|
||||
@echo " * 'binary' - Build skopeo with a container"
|
||||
@echo " * 'binary-local' - Build skopeo locally"
|
||||
@echo " * 'static' - Build statically linked binary"
|
||||
@echo " * 'bin/skopeo' - Build skopeo locally"
|
||||
@echo " * 'test-unit' - Execute unit tests"
|
||||
@echo " * 'test-integration' - Execute integration tests"
|
||||
@echo " * 'validate' - Verify whether there is no conflict and all Go source files have been formatted, linted and vetted"
|
||||
@@ -98,25 +110,37 @@ help:
|
||||
binary: cmd/skopeo
|
||||
${CONTAINER_RUNTIME} build ${BUILD_ARGS} -f Dockerfile.build -t skopeobuildimage .
|
||||
${CONTAINER_RUNTIME} run --rm --security-opt label=disable -v $$(pwd):/src/github.com/containers/skopeo \
|
||||
skopeobuildimage make binary-local $(if $(DEBUG),DEBUG=$(DEBUG)) BUILDTAGS='$(BUILDTAGS)'
|
||||
skopeobuildimage make bin/skopeo $(if $(DEBUG),DEBUG=$(DEBUG)) BUILDTAGS='$(BUILDTAGS)'
|
||||
|
||||
binary-static: cmd/skopeo
|
||||
${CONTAINER_RUNTIME} build ${BUILD_ARGS} -f Dockerfile.build -t skopeobuildimage .
|
||||
${CONTAINER_RUNTIME} run --rm --security-opt label=disable -v $$(pwd):/src/github.com/containers/skopeo \
|
||||
skopeobuildimage make binary-local-static $(if $(DEBUG),DEBUG=$(DEBUG)) BUILDTAGS='$(BUILDTAGS)'
|
||||
# Update nix/nixpkgs.json its latest stable commit
|
||||
.PHONY: nixpkgs
|
||||
nixpkgs:
|
||||
@nix run \
|
||||
-f channel:nixos-20.09 nix-prefetch-git \
|
||||
-c nix-prefetch-git \
|
||||
--no-deepClone \
|
||||
https://github.com/nixos/nixpkgs refs/heads/nixos-20.09 > nix/nixpkgs.json
|
||||
|
||||
# Build statically linked binary
|
||||
.PHONY: static
|
||||
static:
|
||||
@nix build -f nix/
|
||||
mkdir -p ./bin
|
||||
cp -rfp ./result/bin/* ./bin/
|
||||
|
||||
# Build w/o using containers
|
||||
binary-local:
|
||||
$(GPGME_ENV) $(GO) build $(MOD_VENDOR) ${GO_DYN_FLAGS} -ldflags "-X main.gitCommit=${GIT_COMMIT}" -gcflags "$(GOGCFLAGS)" -tags "$(BUILDTAGS)" -o skopeo ./cmd/skopeo
|
||||
|
||||
binary-local-static:
|
||||
$(GPGME_ENV) $(GO) build $(MOD_VENDOR) -ldflags "-extldflags \"-static\" -X main.gitCommit=${GIT_COMMIT}" -gcflags "$(GOGCFLAGS)" -tags "$(BUILDTAGS)" -o skopeo ./cmd/skopeo
|
||||
.PHONY: bin/skopeo
|
||||
bin/skopeo:
|
||||
$(GPGME_ENV) $(GO) build $(MOD_VENDOR) ${GO_DYN_FLAGS} ${SKOPEO_LDFLAGS} -gcflags "$(GOGCFLAGS)" -tags "$(BUILDTAGS)" -o $@ ./cmd/skopeo
|
||||
bin/skopeo.%:
|
||||
GOOS=$(word 2,$(subst ., ,$@)) GOARCH=$(word 3,$(subst ., ,$@)) $(GO) build $(MOD_VENDOR) ${SKOPEO_LDFLAGS} -tags "containers_image_openpgp $(BUILDTAGS)" -o $@ ./cmd/skopeo
|
||||
local-cross: bin/skopeo.darwin.amd64 bin/skopeo.linux.arm bin/skopeo.linux.arm64 bin/skopeo.windows.386.exe bin/skopeo.windows.amd64.exe
|
||||
|
||||
build-container:
|
||||
${CONTAINER_RUNTIME} build ${BUILD_ARGS} -t "$(IMAGE)" .
|
||||
|
||||
$(MANPAGES): %: %.md
|
||||
@sed -e 's/\((skopeo.*\.md)\)//' -e 's/\[\(skopeo.*\)\]/\1/' $< | $(GOMD2MAN) -in /dev/stdin -out $@
|
||||
sed -e 's/\((skopeo.*\.md)\)//' -e 's/\[\(skopeo.*\)\]/\1/' $< | $(GOMD2MAN) -in /dev/stdin -out $@
|
||||
|
||||
docs: $(MANPAGES)
|
||||
|
||||
@@ -126,26 +150,26 @@ docs-in-container:
|
||||
skopeobuildimage make docs $(if $(DEBUG),DEBUG=$(DEBUG)) BUILDTAGS='$(BUILDTAGS)'
|
||||
|
||||
clean:
|
||||
rm -f skopeo docs/*.1
|
||||
rm -rf bin docs/*.1
|
||||
|
||||
install: install-binary install-docs install-completions
|
||||
install -d -m 755 ${SIGSTOREDIR}
|
||||
install -d -m 755 ${CONTAINERSSYSCONFIGDIR}
|
||||
install -m 644 default-policy.json ${CONTAINERSSYSCONFIGDIR}/policy.json
|
||||
install -d -m 755 ${CONTAINERSCONFDIR}
|
||||
install -m 644 default-policy.json ${CONTAINERSCONFDIR}/policy.json
|
||||
install -d -m 755 ${REGISTRIESDDIR}
|
||||
install -m 644 default.yaml ${REGISTRIESDDIR}/default.yaml
|
||||
|
||||
install-binary: ./skopeo
|
||||
install -d -m 755 ${INSTALLDIR}
|
||||
install -m 755 skopeo ${INSTALLDIR}/skopeo
|
||||
install-binary: bin/skopeo
|
||||
install -d -m 755 ${BINDIR}
|
||||
install -m 755 bin/skopeo ${BINDIR}/skopeo
|
||||
|
||||
install-docs: docs
|
||||
install -d -m 755 ${MANINSTALLDIR}/man1
|
||||
install -m 644 docs/*.1 ${MANINSTALLDIR}/man1/
|
||||
install -d -m 755 ${MANDIR}/man1
|
||||
install -m 644 docs/*.1 ${MANDIR}/man1
|
||||
|
||||
install-completions:
|
||||
install -m 755 -d ${BASHINSTALLDIR}
|
||||
install -m 644 completions/bash/skopeo ${BASHINSTALLDIR}/skopeo
|
||||
install -m 755 -d ${BASHCOMPLETIONSDIR}
|
||||
install -m 644 completions/bash/skopeo ${BASHCOMPLETIONSDIR}/skopeo
|
||||
|
||||
shell: build-container
|
||||
$(CONTAINER_RUN) bash
|
||||
@@ -154,7 +178,11 @@ check: validate test-unit test-integration test-system
|
||||
|
||||
# The tests can run out of entropy and block in containers, so replace /dev/random.
|
||||
test-integration: build-container
|
||||
$(CONTAINER_RUN) bash -c 'rm -f /dev/random; ln -sf /dev/urandom /dev/random; SKOPEO_CONTAINER_TESTS=1 BUILDTAGS="$(BUILDTAGS)" hack/make.sh test-integration'
|
||||
$(CONTAINER_RUN) bash -c 'rm -f /dev/random; ln -sf /dev/urandom /dev/random; SKOPEO_CONTAINER_TESTS=1 BUILDTAGS="$(BUILDTAGS)" $(MAKE) test-integration-local'
|
||||
|
||||
# Intended for CI, shortcut 'build-container' since already running inside container.
|
||||
test-integration-local:
|
||||
hack/make.sh test-integration
|
||||
|
||||
# complicated set of options needed to run podman-in-podman
|
||||
test-system: build-container
|
||||
@@ -162,11 +190,15 @@ test-system: build-container
|
||||
$(CONTAINER_CMD) --privileged \
|
||||
-v $$DTEMP:/var/lib/containers:Z -v /run/systemd/journal/socket:/run/systemd/journal/socket \
|
||||
"$(IMAGE)" \
|
||||
bash -c 'BUILDTAGS="$(BUILDTAGS)" hack/make.sh test-system'; \
|
||||
bash -c 'BUILDTAGS="$(BUILDTAGS)" $(MAKE) test-system-local'; \
|
||||
rc=$$?; \
|
||||
$(RM) -rf $$DTEMP; \
|
||||
exit $$rc
|
||||
|
||||
# Intended for CI, shortcut 'build-container' since already running inside container.
|
||||
test-system-local:
|
||||
hack/make.sh test-system
|
||||
|
||||
test-unit: build-container
|
||||
# Just call (make test unit-local) here instead of worrying about environment differences
|
||||
$(CONTAINER_RUN) make test-unit-local BUILDTAGS='$(BUILDTAGS)'
|
||||
|
||||
208
README.md
208
README.md
@@ -7,29 +7,34 @@ skopeo [ as well as the original Docker v2 images.
|
||||
|
||||
Skopeo works with API V2 registries such as Docker registries, the Atomic registry, private registries, local directories and local OCI-layout directories. Skopeo does not require a daemon to be running to perform these operations which consist of:
|
||||
Skopeo works with API V2 container image registries such as [docker.io](https://docker.io) and [quay.io](https://quay.io) registries, private registries, local directories and local OCI-layout directories. Skopeo can perform operations which consist of:
|
||||
|
||||
* Copying an image from and to various storage mechanisms.
|
||||
For example you can copy images from one registry to another, without requiring privilege.
|
||||
* Inspecting a remote image showing its properties including its layers, without requiring you to pull the image to the host.
|
||||
* Deleting an image from an image repository.
|
||||
* Syncing an external image repository to an internal registry for air-gapped deployments.
|
||||
* When required by the repository, skopeo can pass the appropriate credentials and certificates for authentication.
|
||||
|
||||
Skopeo operates on the following image and repository types:
|
||||
|
||||
* containers-storage:docker-reference
|
||||
An image located in a local containers/storage image store. Location and image store specified in /etc/containers/storage.conf
|
||||
An image located in a local containers/storage image store. Both the location and image store are specified in /etc/containers/storage.conf. (This is the backend for [Podman](https://podman.io), [CRI-O](https://cri-o.io), [Buildah](https://buildah.io) and friends)
|
||||
|
||||
* dir:path
|
||||
An existing local directory path storing the manifest, layer tarballs and signatures as individual files. This is a non-standardized format, primarily useful for debugging or noninvasive container inspection.
|
||||
|
||||
* docker://docker-reference
|
||||
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in $HOME/.docker/config.json, which is set e.g. using (docker login).
|
||||
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$XDG_RUNTIME_DIR/containers/auth.json`, which is set using `skopeo login`.
|
||||
|
||||
* docker-archive:path[:docker-reference]
|
||||
An image is stored in the `docker save` formated file. docker-reference is only used when creating such a file, and it must not contain a digest.
|
||||
An image is stored in a `docker save`-formatted file. docker-reference is only used when creating such a file, and it must not contain a digest.
|
||||
|
||||
* docker-daemon:docker-reference
|
||||
An image docker-reference stored in the docker daemon internal storage. docker-reference must contain either a tag or a digest. Alternatively, when reading images, the format can also be docker-daemon:algo:digest (an image ID).
|
||||
@@ -37,138 +42,173 @@ Skopeo works with API V2 registries such as Docker registries, the Atomic regist
|
||||
* oci:path:tag
|
||||
An image tag in a directory compliant with "Open Container Image Layout Specification" at path.
|
||||
|
||||
Inspecting a repository
|
||||
-
|
||||
`skopeo` is able to _inspect_ a repository on a Docker registry and fetch images layers.
|
||||
## Inspecting a repository
|
||||
`skopeo` is able to _inspect_ a repository on a container registry and fetch images layers.
|
||||
The _inspect_ command fetches the repository's manifest and it is able to show you a `docker inspect`-like
|
||||
json output about a whole repository or a tag. This tool, in contrast to `docker inspect`, helps you gather useful information about
|
||||
a repository or a tag before pulling it (using disk space). The inspect command can show you which tags are available for the given
|
||||
repository, the labels the image has, the creation date and operating system of the image and more.
|
||||
|
||||
|
||||
Examples:
|
||||
```sh
|
||||
# show properties of fedora:latest
|
||||
$ skopeo inspect docker://docker.io/fedora
|
||||
|
||||
#### Show properties of fedora:latest
|
||||
```console
|
||||
$ skopeo inspect docker://registry.fedoraproject.org/fedora:latest
|
||||
{
|
||||
"Name": "docker.io/library/fedora",
|
||||
"Tag": "latest",
|
||||
"Digest": "sha256:cfd8f071bf8da7a466748f522406f7ae5908d002af1b1a1c0dcf893e183e5b32",
|
||||
"Name": "registry.fedoraproject.org/fedora",
|
||||
"Digest": "sha256:655721ff613ee766a4126cb5e0d5ae81598e1b0c3bcf7017c36c4d72cb092fe9",
|
||||
"RepoTags": [
|
||||
"20",
|
||||
"21",
|
||||
"22",
|
||||
"23",
|
||||
"heisenbug",
|
||||
"latest",
|
||||
"rawhide"
|
||||
"24",
|
||||
"25",
|
||||
"26-modular",
|
||||
...
|
||||
],
|
||||
"Created": "2016-03-04T18:40:02.92155334Z",
|
||||
"DockerVersion": "1.9.1",
|
||||
"Labels": {},
|
||||
"Created": "2020-04-29T06:48:16Z",
|
||||
"DockerVersion": "1.10.1",
|
||||
"Labels": {
|
||||
"license": "MIT",
|
||||
"name": "fedora",
|
||||
"vendor": "Fedora Project",
|
||||
"version": "32"
|
||||
},
|
||||
"Architecture": "amd64",
|
||||
"Os": "linux",
|
||||
"Layers": [
|
||||
"sha256:236608c7b546e2f4e7223526c74fc71470ba06d46ec82aeb402e704bfdee02a2",
|
||||
"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
|
||||
"sha256:3088721d7dbf674fc0be64cd3cf00c25aab921cacf35fa0e7b1578500a3e1653"
|
||||
],
|
||||
"Env": [
|
||||
"DISTTAG=f32container",
|
||||
"FGC=f32",
|
||||
"container=oci"
|
||||
]
|
||||
}
|
||||
|
||||
# show unverifed image's digest
|
||||
$ skopeo inspect docker://docker.io/fedora:rawhide | jq '.Digest'
|
||||
"sha256:905b4846938c8aef94f52f3e41a11398ae5b40f5855fb0e40ed9c157e721d7f8"
|
||||
```
|
||||
|
||||
Copying images
|
||||
-
|
||||
`skopeo` can copy container images between various storage mechanisms, including:
|
||||
* Docker distribution based registries
|
||||
#### Show container configuration from `fedora:latest`
|
||||
|
||||
- The Docker Hub, OpenShift, GCR, Artifactory, Quay ...
|
||||
```console
|
||||
$ skopeo inspect --config docker://registry.fedoraproject.org/fedora:latest | jq
|
||||
{
|
||||
"created": "2020-04-29T06:48:16Z",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"config": {
|
||||
"Env": [
|
||||
"DISTTAG=f32container",
|
||||
"FGC=f32",
|
||||
"container=oci"
|
||||
],
|
||||
"Cmd": [
|
||||
"/bin/bash"
|
||||
],
|
||||
"Labels": {
|
||||
"license": "MIT",
|
||||
"name": "fedora",
|
||||
"vendor": "Fedora Project",
|
||||
"version": "32"
|
||||
}
|
||||
},
|
||||
"rootfs": {
|
||||
"type": "layers",
|
||||
"diff_ids": [
|
||||
"sha256:a4c0fa2b217d3fd63d51e55a6fd59432e543d499c0df2b1acd48fbe424f2ddd1"
|
||||
]
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"created": "2020-04-29T06:48:16Z",
|
||||
"comment": "Created by Image Factory"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
#### Show unverified image's digest
|
||||
```console
|
||||
$ skopeo inspect docker://registry.fedoraproject.org/fedora:latest | jq '.Digest'
|
||||
"sha256:655721ff613ee766a4126cb5e0d5ae81598e1b0c3bcf7017c36c4d72cb092fe9"
|
||||
```
|
||||
|
||||
## Copying images
|
||||
|
||||
`skopeo` can copy container images between various storage mechanisms, including:
|
||||
* Container registries
|
||||
|
||||
- The Quay, Docker Hub, OpenShift, GCR, Artifactory ...
|
||||
|
||||
* Container Storage backends
|
||||
|
||||
- Docker daemon storage
|
||||
- [github.com/containers/storage](https://github.com/containers/storage) (Backend for [Podman](https://podman.io), [CRI-O](https://cri-o.io), [Buildah](https://buildah.io) and friends)
|
||||
|
||||
- github.com/containers/storage (Backend for CRI-O, Buildah and friends)
|
||||
- Docker daemon storage
|
||||
|
||||
* Local directories
|
||||
|
||||
* Local OCI-layout directories
|
||||
|
||||
```sh
|
||||
$ skopeo copy docker://busybox:1-glibc atomic:myns/unsigned:streaming
|
||||
$ skopeo copy docker://busybox:latest dir:existingemptydirectory
|
||||
$ skopeo copy docker://busybox:latest oci:busybox_ocilayout:latest
|
||||
```console
|
||||
$ skopeo copy docker://quay.io/buildah/stable docker://registry.internal.company.com/buildah
|
||||
$ skopeo copy oci:busybox_ocilayout:latest dir:existingemptydirectory
|
||||
```
|
||||
|
||||
Deleting images
|
||||
-
|
||||
For example,
|
||||
```sh
|
||||
## Deleting images
|
||||
```console
|
||||
$ skopeo delete docker://localhost:5000/imagename:latest
|
||||
```
|
||||
|
||||
Private registries with authentication
|
||||
-
|
||||
When interacting with private registries, `skopeo` first looks for `--creds` (for `skopeo inspect|delete`) or `--src-creds|--dest-creds` (for `skopeo copy`) flags. If those aren't provided, it looks for the Docker's cli config file (usually located at `$HOME/.docker/config.json`) to get the credentials needed to authenticate. The ultimate fallback, as Docker does, is to provide an empty authentication when interacting with those registries.
|
||||
## Syncing registries
|
||||
```console
|
||||
$ skopeo sync --src docker --dest dir registry.example.com/busybox /media/usb
|
||||
```
|
||||
|
||||
Examples:
|
||||
```sh
|
||||
$ cat /home/runcom/.docker/config.json
|
||||
{
|
||||
"auths": {
|
||||
"myregistrydomain.com:5000": {
|
||||
"auth": "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk",
|
||||
"email": "stuf@ex.cm"
|
||||
}
|
||||
}
|
||||
}
|
||||
## Authenticating to a registry
|
||||
|
||||
# we can see I'm already authenticated via docker login so everything will be fine
|
||||
#### Private registries with authentication
|
||||
skopeo uses credentials from the --creds (for skopeo inspect|delete) or --src-creds|--dest-creds (for skopeo copy) flags, if set; otherwise it uses configuration set by skopeo login, podman login, buildah login, or docker login.
|
||||
|
||||
```console
|
||||
$ skopeo login --username USER myregistrydomain.com:5000
|
||||
Password:
|
||||
$ skopeo inspect docker://myregistrydomain.com:5000/busybox
|
||||
{"Tag":"latest","Digest":"sha256:473bb2189d7b913ed7187a33d11e743fdc2f88931122a44d91a301b64419f092","RepoTags":["latest"],"Comment":"","Created":"2016-01-15T18:06:41.282540103Z","ContainerConfig":{"Hostname":"aded96b43f48","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":["/bin/sh","-c","#(nop) CMD [\"sh\"]"],"Image":"9e77fef7a1c9f989988c06620dabc4020c607885b959a2cbd7c2283c91da3e33","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"DockerVersion":"1.8.3","Author":"","Config":{"Hostname":"aded96b43f48","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":["sh"],"Image":"9e77fef7a1c9f989988c06620dabc4020c607885b959a2cbd7c2283c91da3e33","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"Architecture":"amd64","Os":"linux"}
|
||||
$ skopeo logout myregistrydomain.com:5000
|
||||
```
|
||||
|
||||
# let's try now to fake a non existent Docker's config file
|
||||
$ cat /home/runcom/.docker/config.json
|
||||
{}
|
||||
#### Using --creds directly
|
||||
|
||||
$ skopeo inspect docker://myregistrydomain.com:5000/busybox
|
||||
FATA[0000] unauthorized: authentication required
|
||||
|
||||
# passing --creds - we can see that everything goes fine
|
||||
```console
|
||||
$ skopeo inspect --creds=testuser:testpassword docker://myregistrydomain.com:5000/busybox
|
||||
{"Tag":"latest","Digest":"sha256:473bb2189d7b913ed7187a33d11e743fdc2f88931122a44d91a301b64419f092","RepoTags":["latest"],"Comment":"","Created":"2016-01-15T18:06:41.282540103Z","ContainerConfig":{"Hostname":"aded96b43f48","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":["/bin/sh","-c","#(nop) CMD [\"sh\"]"],"Image":"9e77fef7a1c9f989988c06620dabc4020c607885b959a2cbd7c2283c91da3e33","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"DockerVersion":"1.8.3","Author":"","Config":{"Hostname":"aded96b43f48","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":["sh"],"Image":"9e77fef7a1c9f989988c06620dabc4020c607885b959a2cbd7c2283c91da3e33","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"Architecture":"amd64","Os":"linux"}
|
||||
```
|
||||
|
||||
# skopeo copy example:
|
||||
```console
|
||||
$ skopeo copy --src-creds=testuser:testpassword docker://myregistrydomain.com:5000/private oci:local_oci_image
|
||||
```
|
||||
If your cli config is found but it doesn't contain the necessary credentials for the queried registry
|
||||
you'll get an error. You can fix this by either logging in (via `docker login`) or providing `--creds` or `--src-creds|--dest-creds`.
|
||||
|
||||
|
||||
Obtaining skopeo
|
||||
[Obtaining skopeo](./install.md)
|
||||
-
|
||||
|
||||
For a detailed description how to install or build skopeo, see
|
||||
[install.md](./install.md).
|
||||
|
||||
TODO
|
||||
-
|
||||
- list all images on registry?
|
||||
- registry v2 search?
|
||||
- show repo tags via flag or when reference isn't tagged or digested
|
||||
- support rkt/appc image spec
|
||||
|
||||
NOT TODO
|
||||
-
|
||||
- provide a _format_ flag - just use the awesome [jq](https://stedolan.github.io/jq/)
|
||||
|
||||
CONTRIBUTING
|
||||
Contributing
|
||||
-
|
||||
|
||||
Please read the [contribution guide](CONTRIBUTING.md) if you want to collaborate in the project.
|
||||
|
||||
## Commands
|
||||
| Command | Description |
|
||||
| -------------------------------------------------- | ---------------------------------------------------------------------------------------------|
|
||||
| [skopeo-copy(1)](/docs/skopeo-copy.1.md) | Copy an image (manifest, filesystem layers, signatures) from one location to another. |
|
||||
| [skopeo-delete(1)](/docs/skopeo-delete.1.md) | Mark the image-name for later deletion by the registry's garbage collector. |
|
||||
| [skopeo-inspect(1)](/docs/skopeo-inspect.1.md) | Return low-level information about image-name in a registry. |
|
||||
| [skopeo-list-tags(1)](/docs/skopeo-list-tags.1.md) | Return a list of tags for the transport-specific image repository. |
|
||||
| [skopeo-login(1)](/docs/skopeo-login.1.md) | Login to a container registry. |
|
||||
| [skopeo-logout(1)](/docs/skopeo-logout.1.md) | Logout of a container registry. |
|
||||
| [skopeo-manifest-digest(1)](/docs/skopeo-manifest-digest.1.md) | Compute a manifest digest for a manifest-file and write it to standard output. |
|
||||
| [skopeo-standalone-sign(1)](/docs/skopeo-standalone-sign.1.md) | Debugging tool - Publish and sign an image in one step. |
|
||||
| [skopeo-standalone-verify(1)](/docs/skopeo-standalone-verify.1.md)| Verify an image signature. |
|
||||
| [skopeo-sync(1)](/docs/skopeo-sync.1.md) | Synchronize images between container registries and local directories. |
|
||||
|
||||
License
|
||||
-
|
||||
skopeo is licensed under the Apache License, Version 2.0. See
|
||||
|
||||
3
SECURITY.md
Normal file
3
SECURITY.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## Security and Disclosure Information Policy for the skopeo Project
|
||||
|
||||
The skopeo Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/master/SECURITY.md) for the Containers Projects.
|
||||
@@ -4,107 +4,77 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/common/pkg/retry"
|
||||
"github.com/containers/image/v5/copy"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
|
||||
encconfig "github.com/containers/ocicrypt/config"
|
||||
enchelpers "github.com/containers/ocicrypt/helpers"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type copyOptions struct {
|
||||
global *globalOptions
|
||||
srcImage *imageOptions
|
||||
destImage *imageDestOptions
|
||||
additionalTags cli.StringSlice // For docker-archive: destinations, in addition to the name:tag specified as destination, also add these
|
||||
removeSignatures bool // Do not copy signatures from the source image
|
||||
signByFingerprint string // Sign the image using a GPG key with the specified fingerprint
|
||||
format optionalString // Force conversion of the image to a specified format
|
||||
quiet bool // Suppress output information when copying images
|
||||
all bool // Copy all of the images if the source is a list
|
||||
encryptLayer cli.IntSlice // The list of layers to encrypt
|
||||
encryptionKeys cli.StringSlice // Keys needed to encrypt the image
|
||||
decryptionKeys cli.StringSlice // Keys needed to decrypt the image
|
||||
retryOpts *retry.RetryOptions
|
||||
additionalTags []string // For docker-archive: destinations, in addition to the name:tag specified as destination, also add these
|
||||
removeSignatures bool // Do not copy signatures from the source image
|
||||
signByFingerprint string // Sign the image using a GPG key with the specified fingerprint
|
||||
digestFile string // Write digest to this file
|
||||
format optionalString // Force conversion of the image to a specified format
|
||||
quiet bool // Suppress output information when copying images
|
||||
all bool // Copy all of the images if the source is a list
|
||||
encryptLayer []int // The list of layers to encrypt
|
||||
encryptionKeys []string // Keys needed to encrypt the image
|
||||
decryptionKeys []string // Keys needed to decrypt the image
|
||||
}
|
||||
|
||||
func copyCmd(global *globalOptions) cli.Command {
|
||||
func copyCmd(global *globalOptions) *cobra.Command {
|
||||
sharedFlags, sharedOpts := sharedImageFlags()
|
||||
srcFlags, srcOpts := imageFlags(global, sharedOpts, "src-", "screds")
|
||||
destFlags, destOpts := imageDestFlags(global, sharedOpts, "dest-", "dcreds")
|
||||
retryFlags, retryOpts := retryFlags()
|
||||
opts := copyOptions{global: global,
|
||||
srcImage: srcOpts,
|
||||
destImage: destOpts,
|
||||
retryOpts: retryOpts,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "copy [command options] SOURCE-IMAGE DESTINATION-IMAGE",
|
||||
Short: "Copy an IMAGE-NAME from one location to another",
|
||||
Long: fmt.Sprintf(`Container "IMAGE-NAME" uses a "transport":"details" format.
|
||||
|
||||
return cli.Command{
|
||||
Name: "copy",
|
||||
Usage: "Copy an IMAGE-NAME from one location to another",
|
||||
Description: fmt.Sprintf(`
|
||||
Supported transports:
|
||||
%s
|
||||
|
||||
Container "IMAGE-NAME" uses a "transport":"details" format.
|
||||
|
||||
Supported transports:
|
||||
%s
|
||||
|
||||
See skopeo(1) section "IMAGE NAMES" for the expected format
|
||||
`, strings.Join(transports.ListNames(), ", ")),
|
||||
ArgsUsage: "SOURCE-IMAGE DESTINATION-IMAGE",
|
||||
Action: commandAction(opts.run),
|
||||
// FIXME: Do we need to namespace the GPG aspect?
|
||||
Flags: append(append(append([]cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "additional-tag",
|
||||
Usage: "additional tags (supports docker-archive)",
|
||||
Value: &opts.additionalTags, // Surprisingly StringSliceFlag does not support Destination:, but modifies Value: in place.
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "Suppress output information when copying images",
|
||||
Destination: &opts.quiet,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "all, a",
|
||||
Usage: "Copy all images if SOURCE-IMAGE is a list",
|
||||
Destination: &opts.all,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "remove-signatures",
|
||||
Usage: "Do not copy signatures from SOURCE-IMAGE",
|
||||
Destination: &opts.removeSignatures,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "sign-by",
|
||||
Usage: "Sign the image using a GPG key with the specified `FINGERPRINT`",
|
||||
Destination: &opts.signByFingerprint,
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: "format, f",
|
||||
Usage: "`MANIFEST TYPE` (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source)",
|
||||
Value: newOptionalStringValue(&opts.format),
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "encryption-key",
|
||||
Usage: "*Experimental* key with the encryption protocol to use needed to encrypt the image (e.g. jwe:/path/to/key.pem)",
|
||||
Value: &opts.encryptionKeys,
|
||||
},
|
||||
cli.IntSliceFlag{
|
||||
Name: "encrypt-layer",
|
||||
Usage: "*Experimental* the 0-indexed layer indices, with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer)",
|
||||
Value: &opts.encryptLayer,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "decryption-key",
|
||||
Usage: "*Experimental* key needed to decrypt the image",
|
||||
Value: &opts.decryptionKeys,
|
||||
},
|
||||
}, sharedFlags...), srcFlags...), destFlags...),
|
||||
See skopeo(1) section "IMAGE NAMES" for the expected format
|
||||
`, strings.Join(transports.ListNames(), ", ")),
|
||||
RunE: commandAction(opts.run),
|
||||
Example: `skopeo copy docker://quay.io/skopeo/stable:latest docker://registry.example.com/skopeo:latest`,
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
flags := cmd.Flags()
|
||||
flags.AddFlagSet(&sharedFlags)
|
||||
flags.AddFlagSet(&srcFlags)
|
||||
flags.AddFlagSet(&destFlags)
|
||||
flags.AddFlagSet(&retryFlags)
|
||||
flags.StringSliceVar(&opts.additionalTags, "additional-tag", []string{}, "additional tags (supports docker-archive)")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress output information when copying images")
|
||||
flags.BoolVarP(&opts.all, "all", "a", false, "Copy all images if SOURCE-IMAGE is a list")
|
||||
flags.BoolVar(&opts.removeSignatures, "remove-signatures", false, "Do not copy signatures from SOURCE-IMAGE")
|
||||
flags.StringVar(&opts.signByFingerprint, "sign-by", "", "Sign the image using a GPG key with the specified `FINGERPRINT`")
|
||||
flags.StringVar(&opts.digestFile, "digestfile", "", "Write the digest of the pushed image to the specified file")
|
||||
flags.VarP(newOptionalStringValue(&opts.format), "format", "f", `MANIFEST TYPE (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source)`)
|
||||
flags.StringSliceVar(&opts.encryptionKeys, "encryption-key", []string{}, "*Experimental* key with the encryption protocol to use needed to encrypt the image (e.g. jwe:/path/to/key.pem)")
|
||||
flags.IntSliceVar(&opts.encryptLayer, "encrypt-layer", []int{}, "*Experimental* the 0-indexed layer indices, with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer)")
|
||||
flags.StringSliceVar(&opts.decryptionKeys, "decryption-key", []string{}, "*Experimental* key needed to decrypt the image")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (opts *copyOptions) run(args []string, stdout io.Writer) error {
|
||||
@@ -143,15 +113,9 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error {
|
||||
|
||||
var manifestType string
|
||||
if opts.format.present {
|
||||
switch opts.format.value {
|
||||
case "oci":
|
||||
manifestType = imgspecv1.MediaTypeImageManifest
|
||||
case "v2s1":
|
||||
manifestType = manifest.DockerV2Schema1SignedMediaType
|
||||
case "v2s2":
|
||||
manifestType = manifest.DockerV2Schema2MediaType
|
||||
default:
|
||||
return fmt.Errorf("unknown format %q. Choose one of the supported formats: 'oci', 'v2s1', or 'v2s2'", opts.format.value)
|
||||
manifestType, err = parseManifestFormat(opts.format.value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +142,7 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error {
|
||||
imageListSelection = copy.CopyAllImages
|
||||
}
|
||||
|
||||
if len(opts.encryptionKeys.Value()) > 0 && len(opts.decryptionKeys.Value()) > 0 {
|
||||
if len(opts.encryptionKeys) > 0 && len(opts.decryptionKeys) > 0 {
|
||||
return fmt.Errorf("--encryption-key and --decryption-key cannot be specified together")
|
||||
}
|
||||
|
||||
@@ -186,15 +150,15 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error {
|
||||
var encConfig *encconfig.EncryptConfig
|
||||
var decConfig *encconfig.DecryptConfig
|
||||
|
||||
if len(opts.encryptLayer.Value()) > 0 && len(opts.encryptionKeys.Value()) == 0 {
|
||||
if len(opts.encryptLayer) > 0 && len(opts.encryptionKeys) == 0 {
|
||||
return fmt.Errorf("--encrypt-layer can only be used with --encryption-key")
|
||||
}
|
||||
|
||||
if len(opts.encryptionKeys.Value()) > 0 {
|
||||
if len(opts.encryptionKeys) > 0 {
|
||||
// encryption
|
||||
p := opts.encryptLayer.Value()
|
||||
p := opts.encryptLayer
|
||||
encLayers = &p
|
||||
encryptionKeys := opts.encryptionKeys.Value()
|
||||
encryptionKeys := opts.encryptionKeys
|
||||
ecc, err := enchelpers.CreateCryptoConfig(encryptionKeys, []string{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid encryption keys: %v", err)
|
||||
@@ -203,9 +167,9 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error {
|
||||
encConfig = cc.EncryptConfig
|
||||
}
|
||||
|
||||
if len(opts.decryptionKeys.Value()) > 0 {
|
||||
if len(opts.decryptionKeys) > 0 {
|
||||
// decryption
|
||||
decryptionKeys := opts.decryptionKeys.Value()
|
||||
decryptionKeys := opts.decryptionKeys
|
||||
dcc, err := enchelpers.CreateCryptoConfig([]string{}, decryptionKeys)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid decryption keys: %v", err)
|
||||
@@ -214,17 +178,31 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error {
|
||||
decConfig = cc.DecryptConfig
|
||||
}
|
||||
|
||||
_, err = copy.Image(ctx, policyContext, destRef, srcRef, ©.Options{
|
||||
RemoveSignatures: opts.removeSignatures,
|
||||
SignBy: opts.signByFingerprint,
|
||||
ReportWriter: stdout,
|
||||
SourceCtx: sourceCtx,
|
||||
DestinationCtx: destinationCtx,
|
||||
ForceManifestMIMEType: manifestType,
|
||||
ImageListSelection: imageListSelection,
|
||||
OciDecryptConfig: decConfig,
|
||||
OciEncryptLayers: encLayers,
|
||||
OciEncryptConfig: encConfig,
|
||||
})
|
||||
return err
|
||||
return retry.RetryIfNecessary(ctx, func() error {
|
||||
manifestBytes, err := copy.Image(ctx, policyContext, destRef, srcRef, ©.Options{
|
||||
RemoveSignatures: opts.removeSignatures,
|
||||
SignBy: opts.signByFingerprint,
|
||||
ReportWriter: stdout,
|
||||
SourceCtx: sourceCtx,
|
||||
DestinationCtx: destinationCtx,
|
||||
ForceManifestMIMEType: manifestType,
|
||||
ImageListSelection: imageListSelection,
|
||||
OciDecryptConfig: decConfig,
|
||||
OciEncryptLayers: encLayers,
|
||||
OciEncryptConfig: encConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.digestFile != "" {
|
||||
manifestDigest, err := manifest.Digest(manifestBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ioutil.WriteFile(opts.digestFile, []byte(manifestDigest.String()), 0644); err != nil {
|
||||
return fmt.Errorf("Failed to write digest to file %q: %w", opts.digestFile, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, opts.retryOpts)
|
||||
}
|
||||
|
||||
@@ -6,38 +6,44 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/common/pkg/retry"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type deleteOptions struct {
|
||||
global *globalOptions
|
||||
image *imageOptions
|
||||
global *globalOptions
|
||||
image *imageOptions
|
||||
retryOpts *retry.RetryOptions
|
||||
}
|
||||
|
||||
func deleteCmd(global *globalOptions) cli.Command {
|
||||
func deleteCmd(global *globalOptions) *cobra.Command {
|
||||
sharedFlags, sharedOpts := sharedImageFlags()
|
||||
imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "")
|
||||
retryFlags, retryOpts := retryFlags()
|
||||
opts := deleteOptions{
|
||||
global: global,
|
||||
image: imageOpts,
|
||||
global: global,
|
||||
image: imageOpts,
|
||||
retryOpts: retryOpts,
|
||||
}
|
||||
return cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete image IMAGE-NAME",
|
||||
Description: fmt.Sprintf(`
|
||||
Delete an "IMAGE_NAME" from a transport
|
||||
|
||||
Supported transports:
|
||||
%s
|
||||
|
||||
See skopeo(1) section "IMAGE NAMES" for the expected format
|
||||
`, strings.Join(transports.ListNames(), ", ")),
|
||||
ArgsUsage: "IMAGE-NAME",
|
||||
Action: commandAction(opts.run),
|
||||
Flags: append(sharedFlags, imageFlags...),
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete [command options] IMAGE-NAME",
|
||||
Short: "Delete image IMAGE-NAME",
|
||||
Long: fmt.Sprintf(`Delete an "IMAGE_NAME" from a transport
|
||||
Supported transports:
|
||||
%s
|
||||
See skopeo(1) section "IMAGE NAMES" for the expected format
|
||||
`, strings.Join(transports.ListNames(), ", ")),
|
||||
RunE: commandAction(opts.run),
|
||||
Example: `skopeo delete docker://registry.example.com/example/pause:latest`,
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
flags := cmd.Flags()
|
||||
flags.AddFlagSet(&sharedFlags)
|
||||
flags.AddFlagSet(&imageFlags)
|
||||
flags.AddFlagSet(&retryFlags)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (opts *deleteOptions) run(args []string, stdout io.Writer) error {
|
||||
@@ -62,5 +68,8 @@ func (opts *deleteOptions) run(args []string, stdout io.Writer) error {
|
||||
|
||||
ctx, cancel := opts.global.commandTimeoutContext()
|
||||
defer cancel()
|
||||
return ref.DeleteImage(ctx, sys)
|
||||
|
||||
return retry.RetryIfNecessary(ctx, func() error {
|
||||
return ref.DeleteImage(ctx, sys)
|
||||
}, opts.retryOpts)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// optionalBool is a boolean with a separate presence flag.
|
||||
@@ -15,10 +15,18 @@ type optionalBool struct {
|
||||
// optionalBool is a cli.Generic == flag.Value implementation equivalent to
|
||||
// the one underlying flag.Bool, except that it records whether the flag has been set.
|
||||
// This is distinct from optionalBool to (pretend to) force callers to use
|
||||
// newOptionalBool
|
||||
// optionalBoolFlag
|
||||
type optionalBoolValue optionalBool
|
||||
|
||||
func newOptionalBoolValue(p *optionalBool) cli.Generic {
|
||||
func optionalBoolFlag(fs *pflag.FlagSet, p *optionalBool, name, usage string) *pflag.Flag {
|
||||
flag := fs.VarPF(internalNewOptionalBoolValue(p), name, "", usage)
|
||||
flag.NoOptDefVal = "true"
|
||||
return flag
|
||||
}
|
||||
|
||||
// WARNING: Do not directly use this method to define optionalBool flag.
|
||||
// Caller should use optionalBoolFlag
|
||||
func internalNewOptionalBoolValue(p *optionalBool) pflag.Value {
|
||||
p.present = false
|
||||
return (*optionalBoolValue)(p)
|
||||
}
|
||||
@@ -40,6 +48,10 @@ func (ob *optionalBoolValue) String() string {
|
||||
return strconv.FormatBool(ob.value)
|
||||
}
|
||||
|
||||
func (ob *optionalBoolValue) Type() string {
|
||||
return "bool"
|
||||
}
|
||||
|
||||
func (ob *optionalBoolValue) IsBoolFlag() bool {
|
||||
return true
|
||||
}
|
||||
@@ -56,7 +68,7 @@ type optionalString struct {
|
||||
// newoptionalString
|
||||
type optionalStringValue optionalString
|
||||
|
||||
func newOptionalStringValue(p *optionalString) cli.Generic {
|
||||
func newOptionalStringValue(p *optionalString) pflag.Value {
|
||||
p.present = false
|
||||
return (*optionalStringValue)(p)
|
||||
}
|
||||
@@ -74,6 +86,10 @@ func (ob *optionalStringValue) String() string {
|
||||
return ob.value
|
||||
}
|
||||
|
||||
func (ob *optionalStringValue) Type() string {
|
||||
return "string"
|
||||
}
|
||||
|
||||
// optionalInt is a int with a separate presence flag.
|
||||
type optionalInt struct {
|
||||
present bool
|
||||
@@ -86,7 +102,7 @@ type optionalInt struct {
|
||||
// newoptionalIntValue
|
||||
type optionalIntValue optionalInt
|
||||
|
||||
func newOptionalIntValue(p *optionalInt) cli.Generic {
|
||||
func newOptionalIntValue(p *optionalInt) pflag.Value {
|
||||
p.present = false
|
||||
return (*optionalIntValue)(p)
|
||||
}
|
||||
@@ -107,3 +123,7 @@ func (ob *optionalIntValue) String() string {
|
||||
}
|
||||
return strconv.Itoa(int(ob.value))
|
||||
}
|
||||
|
||||
func (ob *optionalIntValue) Type() string {
|
||||
return "int"
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ package main
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestOptionalBoolSet(t *testing.T) {
|
||||
@@ -34,7 +34,7 @@ func TestOptionalBoolSet(t *testing.T) {
|
||||
{"2", false, false},
|
||||
} {
|
||||
var ob optionalBool
|
||||
v := newOptionalBoolValue(&ob)
|
||||
v := internalNewOptionalBoolValue(&ob)
|
||||
require.False(t, ob.present)
|
||||
err := v.Set(c.input)
|
||||
if c.accepted {
|
||||
@@ -51,30 +51,23 @@ func TestOptionalBoolSet(t *testing.T) {
|
||||
// is not called in any possible situation).
|
||||
var globalOB, commandOB optionalBool
|
||||
actionRun := false
|
||||
app := cli.NewApp()
|
||||
app.EnableBashCompletion = true
|
||||
app.Flags = []cli.Flag{
|
||||
cli.GenericFlag{
|
||||
Name: "global-OB",
|
||||
Value: newOptionalBoolValue(&globalOB),
|
||||
},
|
||||
app := &cobra.Command{
|
||||
Use: "app",
|
||||
}
|
||||
app.Commands = []cli.Command{{
|
||||
Name: "cmd",
|
||||
Flags: []cli.Flag{
|
||||
cli.GenericFlag{
|
||||
Name: "command-OB",
|
||||
Value: newOptionalBoolValue(&commandOB),
|
||||
},
|
||||
},
|
||||
Action: func(*cli.Context) error {
|
||||
optionalBoolFlag(app.PersistentFlags(), &globalOB, "global-OB", "")
|
||||
cmd := &cobra.Command{
|
||||
Use: "cmd",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
assert.False(t, globalOB.present)
|
||||
assert.False(t, commandOB.present)
|
||||
actionRun = true
|
||||
return nil
|
||||
},
|
||||
}}
|
||||
err := app.Run([]string{"app", "cmd"})
|
||||
}
|
||||
optionalBoolFlag(cmd.Flags(), &commandOB, "command-OB", "")
|
||||
app.AddCommand(cmd)
|
||||
app.SetArgs([]string{"cmd"})
|
||||
err := app.Execute()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, actionRun)
|
||||
}
|
||||
@@ -90,7 +83,7 @@ func TestOptionalBoolString(t *testing.T) {
|
||||
{optionalBool{present: false, value: false}, ""},
|
||||
} {
|
||||
var ob optionalBool
|
||||
v := newOptionalBoolValue(&ob)
|
||||
v := internalNewOptionalBoolValue(&ob)
|
||||
ob = c.input
|
||||
res := v.String()
|
||||
assert.Equal(t, c.expected, res)
|
||||
@@ -114,23 +107,21 @@ func TestOptionalBoolIsBoolFlag(t *testing.T) {
|
||||
} {
|
||||
var ob optionalBool
|
||||
actionRun := false
|
||||
app := cli.NewApp()
|
||||
app.Commands = []cli.Command{{
|
||||
Name: "cmd",
|
||||
Flags: []cli.Flag{
|
||||
cli.GenericFlag{
|
||||
Name: "OB",
|
||||
Value: newOptionalBoolValue(&ob),
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
app := &cobra.Command{Use: "app"}
|
||||
cmd := &cobra.Command{
|
||||
Use: "cmd",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
assert.Equal(t, c.expectedOB, ob)
|
||||
assert.Equal(t, c.expectedArgs, ([]string)(ctx.Args()))
|
||||
assert.Equal(t, c.expectedArgs, args)
|
||||
actionRun = true
|
||||
return nil
|
||||
},
|
||||
}}
|
||||
err := app.Run(append([]string{"app", "cmd"}, c.input...))
|
||||
}
|
||||
optionalBoolFlag(cmd.Flags(), &ob, "OB", "")
|
||||
app.AddCommand(cmd)
|
||||
|
||||
app.SetArgs(append([]string{"cmd"}, c.input...))
|
||||
err := app.Execute()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, actionRun)
|
||||
}
|
||||
@@ -152,30 +143,23 @@ func TestOptionalStringSet(t *testing.T) {
|
||||
// is not called in any possible situation).
|
||||
var globalOS, commandOS optionalString
|
||||
actionRun := false
|
||||
app := cli.NewApp()
|
||||
app.EnableBashCompletion = true
|
||||
app.Flags = []cli.Flag{
|
||||
cli.GenericFlag{
|
||||
Name: "global-OS",
|
||||
Value: newOptionalStringValue(&globalOS),
|
||||
},
|
||||
app := &cobra.Command{
|
||||
Use: "app",
|
||||
}
|
||||
app.Commands = []cli.Command{{
|
||||
Name: "cmd",
|
||||
Flags: []cli.Flag{
|
||||
cli.GenericFlag{
|
||||
Name: "command-OS",
|
||||
Value: newOptionalStringValue(&commandOS),
|
||||
},
|
||||
},
|
||||
Action: func(*cli.Context) error {
|
||||
app.PersistentFlags().Var(newOptionalStringValue(&globalOS), "global-OS", "")
|
||||
cmd := &cobra.Command{
|
||||
Use: "cmd",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
assert.False(t, globalOS.present)
|
||||
assert.False(t, commandOS.present)
|
||||
actionRun = true
|
||||
return nil
|
||||
},
|
||||
}}
|
||||
err := app.Run([]string{"app", "cmd"})
|
||||
}
|
||||
cmd.Flags().Var(newOptionalStringValue(&commandOS), "command-OS", "")
|
||||
app.AddCommand(cmd)
|
||||
app.SetArgs([]string{"cmd"})
|
||||
err := app.Execute()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, actionRun)
|
||||
}
|
||||
@@ -216,23 +200,22 @@ func TestOptionalStringIsBoolFlag(t *testing.T) {
|
||||
} {
|
||||
var os optionalString
|
||||
actionRun := false
|
||||
app := cli.NewApp()
|
||||
app.Commands = []cli.Command{{
|
||||
Name: "cmd",
|
||||
Flags: []cli.Flag{
|
||||
cli.GenericFlag{
|
||||
Name: "OS",
|
||||
Value: newOptionalStringValue(&os),
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
app := &cobra.Command{
|
||||
Use: "app",
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "cmd",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
assert.Equal(t, c.expectedOS, os)
|
||||
assert.Equal(t, c.expectedArgs, ([]string)(ctx.Args()))
|
||||
assert.Equal(t, c.expectedArgs, args)
|
||||
actionRun = true
|
||||
return nil
|
||||
},
|
||||
}}
|
||||
err := app.Run(append([]string{"app", "cmd"}, c.input...))
|
||||
}
|
||||
cmd.Flags().Var(newOptionalStringValue(&os), "OS", "")
|
||||
app.AddCommand(cmd)
|
||||
app.SetArgs(append([]string{"cmd"}, c.input...))
|
||||
err := app.Execute()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, actionRun)
|
||||
}
|
||||
|
||||
@@ -4,83 +4,84 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
|
||||
"github.com/containers/common/pkg/report"
|
||||
"github.com/containers/common/pkg/retry"
|
||||
"github.com/containers/image/v5/docker"
|
||||
"github.com/containers/image/v5/image"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/transports"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/skopeo/cmd/skopeo/inspect"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// inspectOutput is the output format of (skopeo inspect), primarily so that we can format it with a simple json.MarshalIndent.
|
||||
type inspectOutput struct {
|
||||
Name string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
Digest digest.Digest
|
||||
RepoTags []string
|
||||
Created *time.Time
|
||||
DockerVersion string
|
||||
Labels map[string]string
|
||||
Architecture string
|
||||
Os string
|
||||
Layers []string
|
||||
Env []string
|
||||
}
|
||||
|
||||
type inspectOptions struct {
|
||||
global *globalOptions
|
||||
image *imageOptions
|
||||
raw bool // Output the raw manifest instead of parsing information about the image
|
||||
config bool // Output the raw config blob instead of parsing information about the image
|
||||
global *globalOptions
|
||||
image *imageOptions
|
||||
retryOpts *retry.RetryOptions
|
||||
format string
|
||||
raw bool // Output the raw manifest instead of parsing information about the image
|
||||
config bool // Output the raw config blob instead of parsing information about the image
|
||||
}
|
||||
|
||||
func inspectCmd(global *globalOptions) cli.Command {
|
||||
func inspectCmd(global *globalOptions) *cobra.Command {
|
||||
sharedFlags, sharedOpts := sharedImageFlags()
|
||||
imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "")
|
||||
retryFlags, retryOpts := retryFlags()
|
||||
opts := inspectOptions{
|
||||
global: global,
|
||||
image: imageOpts,
|
||||
global: global,
|
||||
image: imageOpts,
|
||||
retryOpts: retryOpts,
|
||||
}
|
||||
return cli.Command{
|
||||
Name: "inspect",
|
||||
Usage: "Inspect image IMAGE-NAME",
|
||||
Description: fmt.Sprintf(`
|
||||
Return low-level information about "IMAGE-NAME" in a registry/transport
|
||||
cmd := &cobra.Command{
|
||||
Use: "inspect [command options] IMAGE-NAME",
|
||||
Short: "Inspect image IMAGE-NAME",
|
||||
Long: fmt.Sprintf(`Return low-level information about "IMAGE-NAME" in a registry/transport
|
||||
Supported transports:
|
||||
%s
|
||||
|
||||
Supported transports:
|
||||
%s
|
||||
|
||||
See skopeo(1) section "IMAGE NAMES" for the expected format
|
||||
`, strings.Join(transports.ListNames(), ", ")),
|
||||
ArgsUsage: "IMAGE-NAME",
|
||||
Flags: append(append([]cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "raw",
|
||||
Usage: "output raw manifest or configuration",
|
||||
Destination: &opts.raw,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "config",
|
||||
Usage: "output configuration",
|
||||
Destination: &opts.config,
|
||||
},
|
||||
}, sharedFlags...), imageFlags...),
|
||||
Action: commandAction(opts.run),
|
||||
See skopeo(1) section "IMAGE NAMES" for the expected format
|
||||
`, strings.Join(transports.ListNames(), ", ")),
|
||||
RunE: commandAction(opts.run),
|
||||
Example: `skopeo inspect docker://registry.fedoraproject.org/fedora
|
||||
skopeo inspect --config docker://docker.io/alpine
|
||||
skopeo inspect --format "Name: {{.Name}} Digest: {{.Digest}}" docker://registry.access.redhat.com/ubi8`,
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&opts.raw, "raw", false, "output raw manifest or configuration")
|
||||
flags.BoolVar(&opts.config, "config", false, "output configuration")
|
||||
flags.StringVarP(&opts.format, "format", "f", "", "Format the output to a Go template")
|
||||
flags.AddFlagSet(&sharedFlags)
|
||||
flags.AddFlagSet(&imageFlags)
|
||||
flags.AddFlagSet(&retryFlags)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error) {
|
||||
var (
|
||||
rawManifest []byte
|
||||
src types.ImageSource
|
||||
imgInspect *types.ImageInspectInfo
|
||||
data []interface{}
|
||||
)
|
||||
ctx, cancel := opts.global.commandTimeoutContext()
|
||||
defer cancel()
|
||||
|
||||
if len(args) != 1 {
|
||||
return errors.New("Exactly one argument expected")
|
||||
}
|
||||
if opts.raw && opts.format != "" {
|
||||
return errors.New("raw output does not support format option")
|
||||
}
|
||||
imageName := args[0]
|
||||
|
||||
if err := reexecIfNecessaryForImages(imageName); err != nil {
|
||||
@@ -92,9 +93,11 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := parseImageSource(ctx, opts.image, imageName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error parsing image name %q: %v", imageName, err)
|
||||
if err := retry.RetryIfNecessary(ctx, func() error {
|
||||
src, err = parseImageSource(ctx, opts.image, imageName)
|
||||
return err
|
||||
}, opts.retryOpts); err != nil {
|
||||
return errors.Wrapf(err, "Error parsing image name %q", imageName)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@@ -103,9 +106,11 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
||||
}
|
||||
}()
|
||||
|
||||
rawManifest, _, err := src.GetManifest(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error retrieving manifest for image: %v", err)
|
||||
if err := retry.RetryIfNecessary(ctx, func() error {
|
||||
rawManifest, _, err = src.GetManifest(ctx, nil)
|
||||
return err
|
||||
}, opts.retryOpts); err != nil {
|
||||
return errors.Wrapf(err, "Error retrieving manifest for image")
|
||||
}
|
||||
|
||||
if opts.raw && !opts.config {
|
||||
@@ -113,45 +118,65 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error writing manifest to standard output: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
img, err := image.FromUnparsedImage(ctx, sys, image.UnparsedInstance(src, nil))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error parsing manifest for image: %v", err)
|
||||
return errors.Wrapf(err, "Error parsing manifest for image")
|
||||
}
|
||||
|
||||
if opts.config && opts.raw {
|
||||
configBlob, err := img.ConfigBlob(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading configuration blob: %v", err)
|
||||
var configBlob []byte
|
||||
if err := retry.RetryIfNecessary(ctx, func() error {
|
||||
configBlob, err = img.ConfigBlob(ctx)
|
||||
return err
|
||||
}, opts.retryOpts); err != nil {
|
||||
return errors.Wrapf(err, "Error reading configuration blob")
|
||||
}
|
||||
_, err = stdout.Write(configBlob)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error writing configuration blob to standard output: %v", err)
|
||||
return errors.Wrapf(err, "Error writing configuration blob to standard output")
|
||||
}
|
||||
return nil
|
||||
} else if opts.config {
|
||||
config, err := img.OCIConfig(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading OCI-formatted configuration data: %v", err)
|
||||
var config *v1.Image
|
||||
if err := retry.RetryIfNecessary(ctx, func() error {
|
||||
config, err = img.OCIConfig(ctx)
|
||||
return err
|
||||
}, opts.retryOpts); err != nil {
|
||||
return errors.Wrapf(err, "Error reading OCI-formatted configuration data")
|
||||
}
|
||||
if report.IsJSON(opts.format) || opts.format == "" {
|
||||
var out []byte
|
||||
out, err = json.MarshalIndent(config, "", " ")
|
||||
if err == nil {
|
||||
fmt.Fprintf(stdout, "%s\n", string(out))
|
||||
}
|
||||
} else {
|
||||
row := "{{range . }}" + report.NormalizeFormat(opts.format) + "{{end}}"
|
||||
data = append(data, config)
|
||||
err = printTmpl(row, data)
|
||||
}
|
||||
err = json.NewEncoder(stdout).Encode(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error writing OCI-formatted configuration data to standard output: %v", err)
|
||||
return errors.Wrapf(err, "Error writing OCI-formatted configuration data to standard output")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
imgInspect, err := img.Inspect(ctx)
|
||||
if err != nil {
|
||||
if err := retry.RetryIfNecessary(ctx, func() error {
|
||||
imgInspect, err = img.Inspect(ctx)
|
||||
return err
|
||||
}, opts.retryOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
outputData := inspectOutput{
|
||||
|
||||
outputData := inspect.Output{
|
||||
Name: "", // Set below if DockerReference() is known
|
||||
Tag: imgInspect.Tag,
|
||||
// Digest is set below.
|
||||
RepoTags: []string{}, // Possibly overriden for docker.Transport.
|
||||
RepoTags: []string{}, // Possibly overridden for docker.Transport.
|
||||
Created: imgInspect.Created,
|
||||
DockerVersion: imgInspect.DockerVersion,
|
||||
Labels: imgInspect.Labels,
|
||||
@@ -162,7 +187,7 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
||||
}
|
||||
outputData.Digest, err = manifest.Digest(rawManifest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error computing manifest digest: %v", err)
|
||||
return errors.Wrapf(err, "Error computing manifest digest")
|
||||
}
|
||||
if dockerRef := img.Reference().DockerReference(); dockerRef != nil {
|
||||
outputData.Name = dockerRef.Name()
|
||||
@@ -180,15 +205,35 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
||||
// In addition, AWS ECR rejects it with 403 (Forbidden) if the "ecr:ListImages"
|
||||
// action is not allowed.
|
||||
if !strings.Contains(err.Error(), "401") && !strings.Contains(err.Error(), "403") {
|
||||
return fmt.Errorf("Error determining repository tags: %v", err)
|
||||
return errors.Wrapf(err, "Error determining repository tags")
|
||||
}
|
||||
logrus.Warnf("Registry disallows tag list retrieval; skipping")
|
||||
}
|
||||
}
|
||||
out, err := json.MarshalIndent(outputData, "", " ")
|
||||
if report.IsJSON(opts.format) || opts.format == "" {
|
||||
out, err := json.MarshalIndent(outputData, "", " ")
|
||||
if err == nil {
|
||||
fmt.Fprintf(stdout, "%s\n", string(out))
|
||||
}
|
||||
return err
|
||||
}
|
||||
row := "{{range . }}" + report.NormalizeFormat(opts.format) + "{{end}}"
|
||||
data = append(data, outputData)
|
||||
return printTmpl(row, data)
|
||||
}
|
||||
|
||||
func inspectNormalize(row string) string {
|
||||
r := strings.NewReplacer(
|
||||
".ImageID", ".Image",
|
||||
)
|
||||
return r.Replace(row)
|
||||
}
|
||||
|
||||
func printTmpl(row string, data []interface{}) error {
|
||||
t, err := template.New("skopeo inspect").Parse(row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(stdout, "%s\n", string(out))
|
||||
return nil
|
||||
w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
|
||||
return t.Execute(w, data)
|
||||
}
|
||||
|
||||
23
cmd/skopeo/inspect/output.go
Normal file
23
cmd/skopeo/inspect/output.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package inspect
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// Output is the output format of (skopeo inspect),
|
||||
// primarily so that we can format it with a simple json.MarshalIndent.
|
||||
type Output struct {
|
||||
Name string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
Digest digest.Digest
|
||||
RepoTags []string
|
||||
Created *time.Time
|
||||
DockerVersion string
|
||||
Labels map[string]string
|
||||
Architecture string
|
||||
Os string
|
||||
Layers []string
|
||||
Env []string
|
||||
}
|
||||
@@ -7,35 +7,43 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/common/pkg/retry"
|
||||
"github.com/containers/image/v5/directory"
|
||||
"github.com/containers/image/v5/image"
|
||||
"github.com/containers/image/v5/pkg/blobinfocache"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type layersOptions struct {
|
||||
global *globalOptions
|
||||
image *imageOptions
|
||||
global *globalOptions
|
||||
image *imageOptions
|
||||
retryOpts *retry.RetryOptions
|
||||
}
|
||||
|
||||
func layersCmd(global *globalOptions) cli.Command {
|
||||
func layersCmd(global *globalOptions) *cobra.Command {
|
||||
sharedFlags, sharedOpts := sharedImageFlags()
|
||||
imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "")
|
||||
retryFlags, retryOpts := retryFlags()
|
||||
opts := layersOptions{
|
||||
global: global,
|
||||
image: imageOpts,
|
||||
global: global,
|
||||
image: imageOpts,
|
||||
retryOpts: retryOpts,
|
||||
}
|
||||
return cli.Command{
|
||||
Name: "layers",
|
||||
Usage: "Get layers of IMAGE-NAME",
|
||||
ArgsUsage: "IMAGE-NAME [LAYER...]",
|
||||
Hidden: true,
|
||||
Action: commandAction(opts.run),
|
||||
Flags: append(sharedFlags, imageFlags...),
|
||||
cmd := &cobra.Command{
|
||||
Hidden: true,
|
||||
Use: "layers [command options] IMAGE-NAME [LAYER...]",
|
||||
Short: "Get layers of IMAGE-NAME",
|
||||
RunE: commandAction(opts.run),
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
flags := cmd.Flags()
|
||||
flags.AddFlagSet(&sharedFlags)
|
||||
flags.AddFlagSet(&imageFlags)
|
||||
flags.AddFlagSet(&retryFlags)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (opts *layersOptions) run(args []string, stdout io.Writer) (retErr error) {
|
||||
@@ -57,12 +65,20 @@ func (opts *layersOptions) run(args []string, stdout io.Writer) (retErr error) {
|
||||
return err
|
||||
}
|
||||
cache := blobinfocache.DefaultCache(sys)
|
||||
rawSource, err := parseImageSource(ctx, opts.image, imageName)
|
||||
if err != nil {
|
||||
var (
|
||||
rawSource types.ImageSource
|
||||
src types.ImageCloser
|
||||
)
|
||||
if err = retry.RetryIfNecessary(ctx, func() error {
|
||||
rawSource, err = parseImageSource(ctx, opts.image, imageName)
|
||||
return err
|
||||
}, opts.retryOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
src, err := image.FromSource(ctx, sys, rawSource)
|
||||
if err != nil {
|
||||
if err = retry.RetryIfNecessary(ctx, func() error {
|
||||
src, err = image.FromSource(ctx, sys, rawSource)
|
||||
return err
|
||||
}, opts.retryOpts); err != nil {
|
||||
if closeErr := rawSource.Close(); closeErr != nil {
|
||||
return errors.Wrapf(err, " (close error: %v)", closeErr)
|
||||
}
|
||||
@@ -126,8 +142,14 @@ func (opts *layersOptions) run(args []string, stdout io.Writer) (retErr error) {
|
||||
}()
|
||||
|
||||
for _, bd := range blobDigests {
|
||||
r, blobSize, err := rawSource.GetBlob(ctx, types.BlobInfo{Digest: bd.digest, Size: -1}, cache)
|
||||
if err != nil {
|
||||
var (
|
||||
r io.ReadCloser
|
||||
blobSize int64
|
||||
)
|
||||
if err = retry.RetryIfNecessary(ctx, func() error {
|
||||
r, blobSize, err = rawSource.GetBlob(ctx, types.BlobInfo{Digest: bd.digest, Size: -1}, cache)
|
||||
return err
|
||||
}, opts.retryOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := dest.PutBlob(ctx, r, types.BlobInfo{Digest: bd.digest, Size: blobSize}, cache, bd.isConfig); err != nil {
|
||||
@@ -138,8 +160,11 @@ func (opts *layersOptions) run(args []string, stdout io.Writer) (retErr error) {
|
||||
}
|
||||
}
|
||||
|
||||
manifest, _, err := src.Manifest(ctx)
|
||||
if err != nil {
|
||||
var manifest []byte
|
||||
if err = retry.RetryIfNecessary(ctx, func() error {
|
||||
manifest, _, err = src.Manifest(ctx)
|
||||
return err
|
||||
}, opts.retryOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dest.PutManifest(ctx, manifest, nil); err != nil {
|
||||
|
||||
@@ -4,15 +4,16 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/common/pkg/retry"
|
||||
"github.com/containers/image/v5/docker"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
"strings"
|
||||
|
||||
"io"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// tagListOutput is the output format of (skopeo list-tags), primarily so that we can format it with a simple json.MarshalIndent.
|
||||
@@ -22,34 +23,40 @@ type tagListOutput struct {
|
||||
}
|
||||
|
||||
type tagsOptions struct {
|
||||
global *globalOptions
|
||||
image *imageOptions
|
||||
global *globalOptions
|
||||
image *imageOptions
|
||||
retryOpts *retry.RetryOptions
|
||||
}
|
||||
|
||||
func tagsCmd(global *globalOptions) cli.Command {
|
||||
func tagsCmd(global *globalOptions) *cobra.Command {
|
||||
sharedFlags, sharedOpts := sharedImageFlags()
|
||||
imageFlags, imageOpts := dockerImageFlags(global, sharedOpts, "", "")
|
||||
retryFlags, retryOpts := retryFlags()
|
||||
|
||||
opts := tagsOptions{
|
||||
global: global,
|
||||
image: imageOpts,
|
||||
global: global,
|
||||
image: imageOpts,
|
||||
retryOpts: retryOpts,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "list-tags [command options] REPOSITORY-NAME",
|
||||
Short: "List tags in the transport/repository specified by the REPOSITORY-NAME",
|
||||
Long: `Return the list of tags from the transport/repository "REPOSITORY-NAME"
|
||||
|
||||
return cli.Command{
|
||||
Name: "list-tags",
|
||||
Usage: "List tags in the transport/repository specified by the REPOSITORY-NAME",
|
||||
Description: `
|
||||
Return the list of tags from the transport/repository "REPOSITORY-NAME"
|
||||
|
||||
Supported transports:
|
||||
docker
|
||||
Supported transports:
|
||||
docker
|
||||
|
||||
See skopeo-list-tags(1) section "REPOSITORY NAMES" for the expected format
|
||||
`,
|
||||
ArgsUsage: "REPOSITORY-NAME",
|
||||
Flags: append(sharedFlags, imageFlags...),
|
||||
Action: commandAction(opts.run),
|
||||
See skopeo-list-tags(1) section "REPOSITORY NAMES" for the expected format
|
||||
`,
|
||||
RunE: commandAction(opts.run),
|
||||
Example: `skopeo list-tags docker://docker.io/fedora`,
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
flags := cmd.Flags()
|
||||
flags.AddFlagSet(&sharedFlags)
|
||||
flags.AddFlagSet(&imageFlags)
|
||||
flags.AddFlagSet(&retryFlags)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Customized version of the alltransports.ParseImageName and docker.ParseReference that does not place a default tag in the reference
|
||||
@@ -116,8 +123,12 @@ func (opts *tagsOptions) run(args []string, stdout io.Writer) (retErr error) {
|
||||
return err
|
||||
}
|
||||
|
||||
repositoryName, tagListing, err := listDockerTags(ctx, sys, imgRef)
|
||||
if err != nil {
|
||||
var repositoryName string
|
||||
var tagListing []string
|
||||
if err = retry.RetryIfNecessary(ctx, func() error {
|
||||
repositoryName, tagListing, err = listDockerTags(ctx, sys, imgRef)
|
||||
return err
|
||||
}, opts.retryOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Tests the kinds of inputs allowed and expected to the command
|
||||
|
||||
47
cmd/skopeo/login.go
Normal file
47
cmd/skopeo/login.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containers/common/pkg/auth"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type loginOptions struct {
|
||||
global *globalOptions
|
||||
loginOpts auth.LoginOptions
|
||||
getLogin optionalBool
|
||||
tlsVerify optionalBool
|
||||
}
|
||||
|
||||
func loginCmd(global *globalOptions) *cobra.Command {
|
||||
opts := loginOptions{
|
||||
global: global,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "Login to a container registry",
|
||||
Long: "Login to a container registry on a specified server.",
|
||||
RunE: commandAction(opts.run),
|
||||
Example: `skopeo login quay.io`,
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
flags := cmd.Flags()
|
||||
optionalBoolFlag(flags, &opts.tlsVerify, "tls-verify", "require HTTPS and verify certificates when accessing the registry")
|
||||
flags.AddFlagSet(auth.GetLoginFlags(&opts.loginOpts))
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (opts *loginOptions) run(args []string, stdout io.Writer) error {
|
||||
ctx, cancel := opts.global.commandTimeoutContext()
|
||||
defer cancel()
|
||||
opts.loginOpts.Stdout = stdout
|
||||
opts.loginOpts.Stdin = os.Stdin
|
||||
sys := opts.global.newSystemContext()
|
||||
if opts.tlsVerify.present {
|
||||
sys.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.tlsVerify.value)
|
||||
}
|
||||
return auth.Login(ctx, sys, &opts.loginOpts, args)
|
||||
}
|
||||
35
cmd/skopeo/logout.go
Normal file
35
cmd/skopeo/logout.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/containers/common/pkg/auth"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type logoutOptions struct {
|
||||
global *globalOptions
|
||||
logoutOpts auth.LogoutOptions
|
||||
}
|
||||
|
||||
func logoutCmd(global *globalOptions) *cobra.Command {
|
||||
opts := logoutOptions{
|
||||
global: global,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "logout",
|
||||
Short: "Logout of a container registry",
|
||||
Long: "Logout of a container registry on a specified server.",
|
||||
RunE: commandAction(opts.run),
|
||||
Example: `skopeo logout quay.io`,
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
cmd.Flags().AddFlagSet(auth.GetLogoutFlags(&opts.logoutOpts))
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (opts *logoutOptions) run(args []string, stdout io.Writer) error {
|
||||
opts.logoutOpts.Stdout = stdout
|
||||
sys := opts.global.newSystemContext()
|
||||
return auth.Logout(sys, &opts.logoutOpts, args)
|
||||
}
|
||||
@@ -3,20 +3,22 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/v5/signature"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/skopeo/version"
|
||||
"github.com/containers/storage/pkg/reexec"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// gitCommit will be the hash that the binary was built from
|
||||
// and will be populated by the Makefile
|
||||
var gitCommit = ""
|
||||
|
||||
var defaultUserAgent = "skopeo/" + version.Version
|
||||
|
||||
type globalOptions struct {
|
||||
debug bool // Enable debug output
|
||||
tlsVerify optionalBool // Require HTTPS and verify certificates (for docker: and docker-daemon:)
|
||||
@@ -31,96 +33,61 @@ type globalOptions struct {
|
||||
tmpDir string // Path to use for big temporary files
|
||||
}
|
||||
|
||||
// createApp returns a cli.App, and the underlying globalOptions object, to be run or tested.
|
||||
func createApp() (*cli.App, *globalOptions) {
|
||||
// createApp returns a cobra.Command, and the underlying globalOptions object, to be run or tested.
|
||||
func createApp() (*cobra.Command, *globalOptions) {
|
||||
opts := globalOptions{}
|
||||
|
||||
app := cli.NewApp()
|
||||
app.EnableBashCompletion = true
|
||||
app.Name = "skopeo"
|
||||
rootCommand := &cobra.Command{
|
||||
Use: "skopeo",
|
||||
Long: "Various operations with container images and container image registries",
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return opts.before(cmd)
|
||||
},
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
}
|
||||
if gitCommit != "" {
|
||||
app.Version = fmt.Sprintf("%s commit: %s", version.Version, gitCommit)
|
||||
rootCommand.Version = fmt.Sprintf("%s commit: %s", version.Version, gitCommit)
|
||||
} else {
|
||||
app.Version = version.Version
|
||||
rootCommand.Version = version.Version
|
||||
}
|
||||
app.Usage = "Various operations with container images and container image registries"
|
||||
app.Flags = []cli.Flag{
|
||||
cli.DurationFlag{
|
||||
Name: "command-timeout",
|
||||
Usage: "timeout for the command execution",
|
||||
Destination: &opts.commandTimeout,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "enable debug output",
|
||||
Destination: &opts.debug,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "insecure-policy",
|
||||
Usage: "run the tool without any policy check",
|
||||
Destination: &opts.insecurePolicy,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "override-arch",
|
||||
Usage: "use `ARCH` instead of the architecture of the machine for choosing images",
|
||||
Destination: &opts.overrideArch,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "override-os",
|
||||
Usage: "use `OS` instead of the running OS for choosing images",
|
||||
Destination: &opts.overrideOS,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "override-variant",
|
||||
Usage: "use `VARIANT` instead of the running architecture variant for choosing images",
|
||||
Destination: &opts.overrideVariant,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "policy",
|
||||
Usage: "Path to a trust policy file",
|
||||
Destination: &opts.policyPath,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "registries-conf",
|
||||
Usage: "path to the registries.conf file",
|
||||
Destination: &opts.registriesConfPath,
|
||||
Hidden: true,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "registries.d",
|
||||
Usage: "use registry configuration files in `DIR` (e.g. for container signature storage)",
|
||||
Destination: &opts.registriesDirPath,
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: "tls-verify",
|
||||
Usage: "require HTTPS and verify certificates when talking to container registries (defaults to true)",
|
||||
Hidden: true,
|
||||
Value: newOptionalBoolValue(&opts.tlsVerify),
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "tmpdir",
|
||||
Usage: "directory used to store temporary files",
|
||||
Destination: &opts.tmpDir,
|
||||
},
|
||||
// Override default `--version` global flag to enable `-v` shorthand
|
||||
var dummyVersion bool
|
||||
rootCommand.Flags().BoolVarP(&dummyVersion, "version", "v", false, "Version for Skopeo")
|
||||
rootCommand.PersistentFlags().BoolVar(&opts.debug, "debug", false, "enable debug output")
|
||||
flag := optionalBoolFlag(rootCommand.PersistentFlags(), &opts.tlsVerify, "tls-verify", "Require HTTPS and verify certificates when accessing the registry")
|
||||
flag.Hidden = true
|
||||
rootCommand.PersistentFlags().StringVar(&opts.policyPath, "policy", "", "Path to a trust policy file")
|
||||
rootCommand.PersistentFlags().BoolVar(&opts.insecurePolicy, "insecure-policy", false, "run the tool without any policy check")
|
||||
rootCommand.PersistentFlags().StringVar(&opts.registriesDirPath, "registries.d", "", "use registry configuration files in `DIR` (e.g. for container signature storage)")
|
||||
rootCommand.PersistentFlags().StringVar(&opts.overrideArch, "override-arch", "", "use `ARCH` instead of the architecture of the machine for choosing images")
|
||||
rootCommand.PersistentFlags().StringVar(&opts.overrideOS, "override-os", "", "use `OS` instead of the running OS for choosing images")
|
||||
rootCommand.PersistentFlags().StringVar(&opts.overrideVariant, "override-variant", "", "use `VARIANT` instead of the running architecture variant for choosing images")
|
||||
rootCommand.PersistentFlags().DurationVar(&opts.commandTimeout, "command-timeout", 0, "timeout for the command execution")
|
||||
rootCommand.PersistentFlags().StringVar(&opts.registriesConfPath, "registries-conf", "", "path to the registries.conf file")
|
||||
if err := rootCommand.PersistentFlags().MarkHidden("registries-conf"); err != nil {
|
||||
logrus.Fatal("unable to mark registries-conf flag as hidden")
|
||||
}
|
||||
app.Before = opts.before
|
||||
app.Commands = []cli.Command{
|
||||
rootCommand.PersistentFlags().StringVar(&opts.tmpDir, "tmpdir", "", "directory used to store temporary files")
|
||||
rootCommand.AddCommand(
|
||||
copyCmd(&opts),
|
||||
deleteCmd(&opts),
|
||||
inspectCmd(&opts),
|
||||
layersCmd(&opts),
|
||||
tagsCmd(&opts),
|
||||
loginCmd(&opts),
|
||||
logoutCmd(&opts),
|
||||
manifestDigestCmd(),
|
||||
syncCmd(&opts),
|
||||
standaloneSignCmd(),
|
||||
standaloneVerifyCmd(),
|
||||
syncCmd(&opts),
|
||||
tagsCmd(&opts),
|
||||
untrustedSignatureDumpCmd(),
|
||||
}
|
||||
return app, &opts
|
||||
)
|
||||
return rootCommand, &opts
|
||||
}
|
||||
|
||||
// before is run by the cli package for any command, before running the command-specific handler.
|
||||
func (opts *globalOptions) before(ctx *cli.Context) error {
|
||||
func (opts *globalOptions) before(cmd *cobra.Command) error {
|
||||
if opts.debug {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
@@ -134,8 +101,8 @@ func main() {
|
||||
if reexec.Init() {
|
||||
return
|
||||
}
|
||||
app, _ := createApp()
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
rootCmd, _ := createApp()
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -167,3 +134,22 @@ func (opts *globalOptions) commandTimeoutContext() (context.Context, context.Can
|
||||
}
|
||||
return ctx, cancel
|
||||
}
|
||||
|
||||
// newSystemContext returns a *types.SystemContext corresponding to opts.
|
||||
// It is guaranteed to return a fresh instance, so it is safe to make additional updates to it.
|
||||
func (opts *globalOptions) newSystemContext() *types.SystemContext {
|
||||
ctx := &types.SystemContext{
|
||||
RegistriesDirPath: opts.registriesDirPath,
|
||||
ArchitectureChoice: opts.overrideArch,
|
||||
OSChoice: opts.overrideOS,
|
||||
VariantChoice: opts.overrideVariant,
|
||||
SystemRegistriesConfPath: opts.registriesConfPath,
|
||||
BigFilesTemporaryDir: opts.tmpDir,
|
||||
DockerRegistryUserAgent: defaultUserAgent,
|
||||
}
|
||||
// DEPRECATED: We support this for backward compatibility, but override it if a per-image flag is provided.
|
||||
if opts.tlsVerify.present {
|
||||
ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.tlsVerify.value)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -1,14 +1,51 @@
|
||||
package main
|
||||
|
||||
import "bytes"
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// runSkopeo creates an app object and runs it with args, with an implied first "skopeo".
|
||||
// Returns output intended for stdout and the returned error, if any.
|
||||
func runSkopeo(args ...string) (string, error) {
|
||||
app, _ := createApp()
|
||||
stdout := bytes.Buffer{}
|
||||
app.Writer = &stdout
|
||||
args = append([]string{"skopeo"}, args...)
|
||||
err := app.Run(args)
|
||||
app.SetOut(&stdout)
|
||||
app.SetArgs(args)
|
||||
err := app.Execute()
|
||||
return stdout.String(), err
|
||||
}
|
||||
|
||||
func TestGlobalOptionsNewSystemContext(t *testing.T) {
|
||||
// Default state
|
||||
opts, _ := fakeGlobalOptions(t, []string{})
|
||||
res := opts.newSystemContext()
|
||||
assert.Equal(t, &types.SystemContext{
|
||||
// User-Agent is set by default.
|
||||
DockerRegistryUserAgent: defaultUserAgent,
|
||||
}, res)
|
||||
// Set everything to non-default values.
|
||||
opts, _ = fakeGlobalOptions(t, []string{
|
||||
"--registries.d", "/srv/registries.d",
|
||||
"--override-arch", "overridden-arch",
|
||||
"--override-os", "overridden-os",
|
||||
"--override-variant", "overridden-variant",
|
||||
"--tmpdir", "/srv",
|
||||
"--registries-conf", "/srv/registries.conf",
|
||||
"--tls-verify=false",
|
||||
})
|
||||
res = opts.newSystemContext()
|
||||
assert.Equal(t, &types.SystemContext{
|
||||
RegistriesDirPath: "/srv/registries.d",
|
||||
ArchitectureChoice: "overridden-arch",
|
||||
OSChoice: "overridden-os",
|
||||
VariantChoice: "overridden-variant",
|
||||
BigFilesTemporaryDir: "/srv",
|
||||
SystemRegistriesConfPath: "/srv/registries.conf",
|
||||
DockerInsecureSkipTLSVerify: types.OptionalBoolTrue,
|
||||
DockerRegistryUserAgent: defaultUserAgent,
|
||||
}, res)
|
||||
}
|
||||
|
||||
@@ -7,20 +7,22 @@ import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type manifestDigestOptions struct {
|
||||
}
|
||||
|
||||
func manifestDigestCmd() cli.Command {
|
||||
opts := manifestDigestOptions{}
|
||||
return cli.Command{
|
||||
Name: "manifest-digest",
|
||||
Usage: "Compute a manifest digest of a file",
|
||||
ArgsUsage: "MANIFEST",
|
||||
Action: commandAction(opts.run),
|
||||
func manifestDigestCmd() *cobra.Command {
|
||||
var opts manifestDigestOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "manifest-digest MANIFEST",
|
||||
Short: "Compute a manifest digest of a file",
|
||||
RunE: commandAction(opts.run),
|
||||
Example: "skopeo manifest-digest manifest.json",
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (opts *manifestDigestOptions) run(args []string, stdout io.Writer) error {
|
||||
|
||||
@@ -17,8 +17,8 @@ func TestManifestDigest(t *testing.T) {
|
||||
}
|
||||
|
||||
// Error reading manifest
|
||||
out, err := runSkopeo("manifest-digest", "/this/doesnt/exist")
|
||||
assertTestFailed(t, out, err, "/this/doesnt/exist")
|
||||
out, err := runSkopeo("manifest-digest", "/this/does/not/exist")
|
||||
assertTestFailed(t, out, err, "/this/does/not/exist")
|
||||
|
||||
// Error computing manifest
|
||||
out, err = runSkopeo("manifest-digest", "fixtures/v2s1-invalid-signatures.manifest.json")
|
||||
|
||||
@@ -8,28 +8,24 @@ import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/containers/image/v5/signature"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type standaloneSignOptions struct {
|
||||
output string // Output file path
|
||||
}
|
||||
|
||||
func standaloneSignCmd() cli.Command {
|
||||
func standaloneSignCmd() *cobra.Command {
|
||||
opts := standaloneSignOptions{}
|
||||
return cli.Command{
|
||||
Name: "standalone-sign",
|
||||
Usage: "Create a signature using local files",
|
||||
ArgsUsage: "MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT",
|
||||
Action: commandAction(opts.run),
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "output, o",
|
||||
Usage: "output the signature to `SIGNATURE`",
|
||||
Destination: &opts.output,
|
||||
},
|
||||
},
|
||||
cmd := &cobra.Command{
|
||||
Use: "standalone-sign [command options] MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT",
|
||||
Short: "Create a signature using local files",
|
||||
RunE: commandAction(opts.run),
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
flags := cmd.Flags()
|
||||
flags.StringVarP(&opts.output, "output", "o", "", "output the signature to `SIGNATURE`")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (opts *standaloneSignOptions) run(args []string, stdout io.Writer) error {
|
||||
@@ -64,14 +60,15 @@ func (opts *standaloneSignOptions) run(args []string, stdout io.Writer) error {
|
||||
type standaloneVerifyOptions struct {
|
||||
}
|
||||
|
||||
func standaloneVerifyCmd() cli.Command {
|
||||
func standaloneVerifyCmd() *cobra.Command {
|
||||
opts := standaloneVerifyOptions{}
|
||||
return cli.Command{
|
||||
Name: "standalone-verify",
|
||||
Usage: "Verify a signature using local files",
|
||||
ArgsUsage: "MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT SIGNATURE",
|
||||
Action: commandAction(opts.run),
|
||||
cmd := &cobra.Command{
|
||||
Use: "standalone-verify MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT SIGNATURE",
|
||||
Short: "Verify a signature using local files",
|
||||
RunE: commandAction(opts.run),
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (opts *standaloneVerifyOptions) run(args []string, stdout io.Writer) error {
|
||||
@@ -115,15 +112,16 @@ func (opts *standaloneVerifyOptions) run(args []string, stdout io.Writer) error
|
||||
type untrustedSignatureDumpOptions struct {
|
||||
}
|
||||
|
||||
func untrustedSignatureDumpCmd() cli.Command {
|
||||
func untrustedSignatureDumpCmd() *cobra.Command {
|
||||
opts := untrustedSignatureDumpOptions{}
|
||||
return cli.Command{
|
||||
Name: "untrusted-signature-dump-without-verification",
|
||||
Usage: "Dump contents of a signature WITHOUT VERIFYING IT",
|
||||
ArgsUsage: "SIGNATURE",
|
||||
Hidden: true,
|
||||
Action: commandAction(opts.run),
|
||||
cmd := &cobra.Command{
|
||||
Use: "untrusted-signature-dump-without-verification SIGNATURE",
|
||||
Short: "Dump contents of a signature WITHOUT VERIFYING IT",
|
||||
RunE: commandAction(opts.run),
|
||||
Hidden: true,
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (opts *untrustedSignatureDumpOptions) run(args []string, stdout io.Writer) error {
|
||||
|
||||
@@ -58,8 +58,8 @@ func TestStandaloneSign(t *testing.T) {
|
||||
|
||||
// Error reading manifest
|
||||
out, err := runSkopeo("standalone-sign", "-o", "/dev/null",
|
||||
"/this/doesnt/exist", dockerReference, fixturesTestKeyFingerprint)
|
||||
assertTestFailed(t, out, err, "/this/doesnt/exist")
|
||||
"/this/does/not/exist", dockerReference, fixturesTestKeyFingerprint)
|
||||
assertTestFailed(t, out, err, "/this/does/not/exist")
|
||||
|
||||
// Invalid Docker reference
|
||||
out, err = runSkopeo("standalone-sign", "-o", "/dev/null",
|
||||
@@ -117,14 +117,14 @@ func TestStandaloneVerify(t *testing.T) {
|
||||
}
|
||||
|
||||
// Error reading manifest
|
||||
out, err := runSkopeo("standalone-verify", "/this/doesnt/exist",
|
||||
out, err := runSkopeo("standalone-verify", "/this/does/not/exist",
|
||||
dockerReference, fixturesTestKeyFingerprint, signaturePath)
|
||||
assertTestFailed(t, out, err, "/this/doesnt/exist")
|
||||
assertTestFailed(t, out, err, "/this/does/not/exist")
|
||||
|
||||
// Error reading signature
|
||||
out, err = runSkopeo("standalone-verify", manifestPath,
|
||||
dockerReference, fixturesTestKeyFingerprint, "/this/doesnt/exist")
|
||||
assertTestFailed(t, out, err, "/this/doesnt/exist")
|
||||
dockerReference, fixturesTestKeyFingerprint, "/this/does/not/exist")
|
||||
assertTestFailed(t, out, err, "/this/does/not/exist")
|
||||
|
||||
// Error verifying signature
|
||||
out, err = runSkopeo("standalone-verify", manifestPath,
|
||||
@@ -151,8 +151,8 @@ func TestUntrustedSignatureDump(t *testing.T) {
|
||||
|
||||
// Error reading manifest
|
||||
out, err := runSkopeo("untrusted-signature-dump-without-verification",
|
||||
"/this/doesnt/exist")
|
||||
assertTestFailed(t, out, err, "/this/doesnt/exist")
|
||||
"/this/does/not/exist")
|
||||
assertTestFailed(t, out, err, "/this/does/not/exist")
|
||||
|
||||
// Error reading signature (input is not a signature)
|
||||
out, err = runSkopeo("untrusted-signature-dump-without-verification", "fixtures/image.manifest.json")
|
||||
|
||||
@@ -8,40 +8,46 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/common/pkg/retry"
|
||||
"github.com/containers/image/v5/copy"
|
||||
"github.com/containers/image/v5/directory"
|
||||
"github.com/containers/image/v5/docker"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// syncOptions contains information retrieved from the skopeo sync command line.
|
||||
type syncOptions struct {
|
||||
global *globalOptions // Global (not command dependant) skopeo options
|
||||
global *globalOptions // Global (not command dependent) skopeo options
|
||||
srcImage *imageOptions // Source image options
|
||||
destImage *imageDestOptions // Destination image options
|
||||
removeSignatures bool // Do not copy signatures from the source image
|
||||
signByFingerprint string // Sign the image using a GPG key with the specified fingerprint
|
||||
source string // Source repository name
|
||||
destination string // Destination registry name
|
||||
scoped bool // When true, namespace copied images at destination using the source repository name
|
||||
retryOpts *retry.RetryOptions
|
||||
removeSignatures bool // Do not copy signatures from the source image
|
||||
signByFingerprint string // Sign the image using a GPG key with the specified fingerprint
|
||||
format optionalString // Force conversion of the image to a specified format
|
||||
source string // Source repository name
|
||||
destination string // Destination registry name
|
||||
scoped bool // When true, namespace copied images at destination using the source repository name
|
||||
all bool // Copy all of the images if an image in the source is a list
|
||||
}
|
||||
|
||||
// repoDescriptor contains information of a single repository used as a sync source.
|
||||
type repoDescriptor struct {
|
||||
DirBasePath string // base path when source is 'dir'
|
||||
TaggedImages []types.ImageReference // List of tagged image found for the repository
|
||||
Context *types.SystemContext // SystemContext for the sync command
|
||||
DirBasePath string // base path when source is 'dir'
|
||||
ImageRefs []types.ImageReference // List of tagged image found for the repository
|
||||
Context *types.SystemContext // SystemContext for the sync command
|
||||
}
|
||||
|
||||
// tlsVerify is an implementation of the Unmarshaler interface, used to
|
||||
// tlsVerifyConfig is an implementation of the Unmarshaler interface, used to
|
||||
// customize the unmarshaling behaviour of the tls-verify YAML key.
|
||||
type tlsVerifyConfig struct {
|
||||
skip types.OptionalBool // skip TLS verification check (false by default)
|
||||
@@ -50,72 +56,59 @@ type tlsVerifyConfig struct {
|
||||
// registrySyncConfig contains information about a single registry, read from
|
||||
// the source YAML file
|
||||
type registrySyncConfig struct {
|
||||
Images map[string][]string // Images map images name to slices with the images' tags
|
||||
Credentials types.DockerAuthConfig // Username and password used to authenticate with the registry
|
||||
TLSVerify tlsVerifyConfig `yaml:"tls-verify"` // TLS verification mode (enabled by default)
|
||||
CertDir string `yaml:"cert-dir"` // Path to the TLS certificates of the registry
|
||||
Images map[string][]string // Images map images name to slices with the images' references (tags, digests)
|
||||
ImagesByTagRegex map[string]string `yaml:"images-by-tag-regex"` // Images map images name to regular expression with the images' tags
|
||||
Credentials types.DockerAuthConfig // Username and password used to authenticate with the registry
|
||||
TLSVerify tlsVerifyConfig `yaml:"tls-verify"` // TLS verification mode (enabled by default)
|
||||
CertDir string `yaml:"cert-dir"` // Path to the TLS certificates of the registry
|
||||
}
|
||||
|
||||
// sourceConfig contains all registries information read from the source YAML file
|
||||
type sourceConfig map[string]registrySyncConfig
|
||||
|
||||
func syncCmd(global *globalOptions) cli.Command {
|
||||
func syncCmd(global *globalOptions) *cobra.Command {
|
||||
sharedFlags, sharedOpts := sharedImageFlags()
|
||||
srcFlags, srcOpts := dockerImageFlags(global, sharedOpts, "src-", "screds")
|
||||
destFlags, destOpts := dockerImageFlags(global, sharedOpts, "dest-", "dcreds")
|
||||
retryFlags, retryOpts := retryFlags()
|
||||
|
||||
opts := syncOptions{
|
||||
global: global,
|
||||
srcImage: srcOpts,
|
||||
destImage: &imageDestOptions{imageOptions: destOpts},
|
||||
retryOpts: retryOpts,
|
||||
}
|
||||
|
||||
return cli.Command{
|
||||
Name: "sync",
|
||||
Usage: "Synchronize one or more images from one location to another",
|
||||
Description: fmt.Sprint(`
|
||||
cmd := &cobra.Command{
|
||||
Use: "sync [command options] --src SOURCE-LOCATION --dest DESTINATION-LOCATION SOURCE DESTINATION",
|
||||
Short: "Synchronize one or more images from one location to another",
|
||||
Long: fmt.Sprint(`Copy all the images from a SOURCE to a DESTINATION.
|
||||
|
||||
Copy all the images from a SOURCE to a DESTINATION.
|
||||
Allowed SOURCE transports (specified with --src): docker, dir, yaml.
|
||||
Allowed DESTINATION transports (specified with --dest): docker, dir.
|
||||
|
||||
Allowed SOURCE transports (specified with --src): docker, dir, yaml.
|
||||
Allowed DESTINATION transports (specified with --dest): docker, dir.
|
||||
|
||||
See skopeo-sync(1) for details.
|
||||
`),
|
||||
ArgsUsage: "--src SOURCE-LOCATION --dest DESTINATION-LOCATION SOURCE DESTINATION",
|
||||
Action: commandAction(opts.run),
|
||||
// FIXME: Do we need to namespace the GPG aspect?
|
||||
Flags: append(append(append([]cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "remove-signatures",
|
||||
Usage: "Do not copy signatures from SOURCE images",
|
||||
Destination: &opts.removeSignatures,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "sign-by",
|
||||
Usage: "Sign the image using a GPG key with the specified `FINGERPRINT`",
|
||||
Destination: &opts.signByFingerprint,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "src, s",
|
||||
Usage: "SOURCE transport type",
|
||||
Destination: &opts.source,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "dest, d",
|
||||
Usage: "DESTINATION transport type",
|
||||
Destination: &opts.destination,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "scoped",
|
||||
Usage: "Images at DESTINATION are prefix using the full source image path as scope",
|
||||
Destination: &opts.scoped,
|
||||
},
|
||||
}, sharedFlags...), srcFlags...), destFlags...),
|
||||
See skopeo-sync(1) for details.
|
||||
`),
|
||||
RunE: commandAction(opts.run),
|
||||
Example: `skopeo sync --src docker --dest dir --scoped registry.example.com/busybox /media/usb`,
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&opts.removeSignatures, "remove-signatures", false, "Do not copy signatures from SOURCE images")
|
||||
flags.StringVar(&opts.signByFingerprint, "sign-by", "", "Sign the image using a GPG key with the specified `FINGERPRINT`")
|
||||
flags.VarP(newOptionalStringValue(&opts.format), "format", "f", `MANIFEST TYPE (oci, v2s1, or v2s2) to use when syncing image(s) to a destination (default is manifest type of source)`)
|
||||
flags.StringVarP(&opts.source, "src", "s", "", "SOURCE transport type")
|
||||
flags.StringVarP(&opts.destination, "dest", "d", "", "DESTINATION transport type")
|
||||
flags.BoolVar(&opts.scoped, "scoped", false, "Images at DESTINATION are prefix using the full source image path as scope")
|
||||
flags.BoolVarP(&opts.all, "all", "a", false, "Copy all images if SOURCE-IMAGE is a list")
|
||||
flags.AddFlagSet(&sharedFlags)
|
||||
flags.AddFlagSet(&srcFlags)
|
||||
flags.AddFlagSet(&destFlags)
|
||||
flags.AddFlagSet(&retryFlags)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// unmarshalYAML is the implementation of the Unmarshaler interface method
|
||||
// UnmarshalYAML is the implementation of the Unmarshaler interface method
|
||||
// method for the tlsVerifyConfig type.
|
||||
// It unmarshals the 'tls-verify' YAML key so that, when they key is not
|
||||
// specified, tls verification is enforced.
|
||||
@@ -144,6 +137,18 @@ func newSourceConfig(yamlFile string) (sourceConfig, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// parseRepositoryReference parses input into a reference.Named, and verifies that it names a repository, not an image.
|
||||
func parseRepositoryReference(input string) (reference.Named, error) {
|
||||
ref, err := reference.ParseNormalizedNamed(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !reference.IsNameOnly(ref) {
|
||||
return nil, errors.Errorf("input names a reference, not a repository")
|
||||
}
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
// destinationReference creates an image reference using the provided transport.
|
||||
// It returns a image reference to be used as destination of an image copy and
|
||||
// any error encountered.
|
||||
@@ -157,15 +162,14 @@ func destinationReference(destination string, transport string) (types.ImageRefe
|
||||
case directory.Transport.Name():
|
||||
_, err := os.Stat(destination)
|
||||
if err == nil {
|
||||
return nil, errors.Errorf(fmt.Sprintf("Refusing to overwrite destination directory %q", destination))
|
||||
return nil, errors.Errorf("Refusing to overwrite destination directory %q", destination)
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, errors.Wrap(err, "Destination directory could not be used")
|
||||
}
|
||||
// the directory holding the image must be created here
|
||||
if err = os.MkdirAll(destination, 0755); err != nil {
|
||||
return nil, errors.Wrapf(err, fmt.Sprintf("Error creating directory for image %s",
|
||||
destination))
|
||||
return nil, errors.Wrapf(err, "Error creating directory for image %s", destination)
|
||||
}
|
||||
imageTransport = directory.Transport
|
||||
default:
|
||||
@@ -175,21 +179,26 @@ func destinationReference(destination string, transport string) (types.ImageRefe
|
||||
|
||||
destRef, err := imageTransport.ParseReference(destination)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, fmt.Sprintf("Cannot obtain a valid image reference for transport %q and reference %q", imageTransport.Name(), destination))
|
||||
return nil, errors.Wrapf(err, "Cannot obtain a valid image reference for transport %q and reference %q", imageTransport.Name(), destination)
|
||||
}
|
||||
|
||||
return destRef, nil
|
||||
}
|
||||
|
||||
// getImageTags retrieves all the tags associated to an image hosted on a
|
||||
// container registry.
|
||||
// getImageTags lists all tags in a repository.
|
||||
// It returns a string slice of tags and any error encountered.
|
||||
func getImageTags(ctx context.Context, sysCtx *types.SystemContext, imgRef types.ImageReference) ([]string, error) {
|
||||
name := imgRef.DockerReference().Name()
|
||||
func getImageTags(ctx context.Context, sysCtx *types.SystemContext, repoRef reference.Named) ([]string, error) {
|
||||
name := repoRef.Name()
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"image": name,
|
||||
}).Info("Getting tags")
|
||||
tags, err := docker.GetRepositoryTags(ctx, sysCtx, imgRef)
|
||||
// Ugly: NewReference rejects IsNameOnly references, and GetRepositoryTags ignores the tag/digest.
|
||||
// So, we use TagNameOnly here only to shut up NewReference
|
||||
dockerRef, err := docker.NewReference(reference.TagNameOnly(repoRef))
|
||||
if err != nil {
|
||||
return nil, err // Should never happen for a reference with tag and no digest
|
||||
}
|
||||
tags, err := docker.GetRepositoryTags(ctx, sysCtx, dockerRef)
|
||||
|
||||
switch err := err.(type) {
|
||||
case nil:
|
||||
@@ -200,51 +209,38 @@ func getImageTags(ctx context.Context, sysCtx *types.SystemContext, imgRef types
|
||||
logrus.Warnf("Registry disallows tag list retrieval: %s", err)
|
||||
break
|
||||
default:
|
||||
return tags, errors.Wrapf(err, fmt.Sprintf("Error determining repository tags for image %s", name))
|
||||
return tags, errors.Wrapf(err, "Error determining repository tags for image %s", name)
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// isTagSpecified checks if an image name includes a tag and returns any errors
|
||||
// encountered.
|
||||
func isTagSpecified(imageName string) (bool, error) {
|
||||
normNamed, err := reference.ParseNormalizedNamed(imageName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
tagged := !reference.IsNameOnly(normNamed)
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"imagename": imageName,
|
||||
"tagged": tagged,
|
||||
}).Info("Tag presence check")
|
||||
return tagged, nil
|
||||
}
|
||||
|
||||
// imagesTopCopyFromRepo builds a list of image references from the tags
|
||||
// found in the source repository.
|
||||
// imagesToCopyFromRepo builds a list of image references from the tags
|
||||
// found in a source repository.
|
||||
// It returns an image reference slice with as many elements as the tags found
|
||||
// and any error encountered.
|
||||
func imagesToCopyFromRepo(repoReference types.ImageReference, repoName string, sourceCtx *types.SystemContext) ([]types.ImageReference, error) {
|
||||
var sourceReferences []types.ImageReference
|
||||
tags, err := getImageTags(context.Background(), sourceCtx, repoReference)
|
||||
func imagesToCopyFromRepo(sys *types.SystemContext, repoRef reference.Named) ([]types.ImageReference, error) {
|
||||
tags, err := getImageTags(context.Background(), sys, repoRef)
|
||||
if err != nil {
|
||||
return sourceReferences, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sourceReferences []types.ImageReference
|
||||
for _, tag := range tags {
|
||||
imageAndTag := fmt.Sprintf("%s:%s", repoName, tag)
|
||||
ref, err := docker.ParseReference(imageAndTag)
|
||||
taggedRef, err := reference.WithTag(repoRef, tag)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, fmt.Sprintf("Cannot obtain a valid image reference for transport %q and reference %q", docker.Transport.Name(), imageAndTag))
|
||||
return nil, errors.Wrapf(err, "Error creating a reference for repository %s and tag %q", repoRef.Name(), tag)
|
||||
}
|
||||
ref, err := docker.NewReference(taggedRef)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Cannot obtain a valid image reference for transport %q and reference %s", docker.Transport.Name(), taggedRef.String())
|
||||
}
|
||||
sourceReferences = append(sourceReferences, ref)
|
||||
}
|
||||
return sourceReferences, nil
|
||||
}
|
||||
|
||||
// imagesTopCopyFromDir builds a list of image references from the images found
|
||||
// imagesToCopyFromDir builds a list of image references from the images found
|
||||
// in the source directory.
|
||||
// It returns an image reference slice with as many elements as the images found
|
||||
// and any error encountered.
|
||||
@@ -258,7 +254,7 @@ func imagesToCopyFromDir(dirPath string) ([]types.ImageReference, error) {
|
||||
dirname := filepath.Dir(path)
|
||||
ref, err := directory.Transport.ParseReference(dirname)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, fmt.Sprintf("Cannot obtain a valid image reference for transport %q and reference %q", directory.Transport.Name(), dirname))
|
||||
return errors.Wrapf(err, "Cannot obtain a valid image reference for transport %q and reference %q", directory.Transport.Name(), dirname)
|
||||
}
|
||||
sourceReferences = append(sourceReferences, ref)
|
||||
return filepath.SkipDir
|
||||
@@ -268,86 +264,144 @@ func imagesToCopyFromDir(dirPath string) ([]types.ImageReference, error) {
|
||||
|
||||
if err != nil {
|
||||
return sourceReferences,
|
||||
errors.Wrapf(err, fmt.Sprintf("Error walking the path %q", dirPath))
|
||||
errors.Wrapf(err, "Error walking the path %q", dirPath)
|
||||
}
|
||||
|
||||
return sourceReferences, nil
|
||||
}
|
||||
|
||||
// imagesTopCopyFromDir builds a list of repository descriptors from the images
|
||||
// imagesToCopyFromRegistry builds a list of repository descriptors from the images
|
||||
// in a registry configuration.
|
||||
// It returns a repository descriptors slice with as many elements as the images
|
||||
// found and any error encountered. Each element of the slice is a list of
|
||||
// tagged image references, to be used as sync source.
|
||||
// image references, to be used as sync source.
|
||||
func imagesToCopyFromRegistry(registryName string, cfg registrySyncConfig, sourceCtx types.SystemContext) ([]repoDescriptor, error) {
|
||||
serverCtx := &sourceCtx
|
||||
// override ctx with per-registryName options
|
||||
serverCtx.DockerCertPath = cfg.CertDir
|
||||
serverCtx.DockerDaemonCertPath = cfg.CertDir
|
||||
serverCtx.DockerDaemonInsecureSkipTLSVerify = (cfg.TLSVerify.skip == types.OptionalBoolTrue)
|
||||
serverCtx.DockerInsecureSkipTLSVerify = cfg.TLSVerify.skip
|
||||
if cfg.Credentials != (types.DockerAuthConfig{}) {
|
||||
serverCtx.DockerAuthConfig = &cfg.Credentials
|
||||
}
|
||||
var repoDescList []repoDescriptor
|
||||
for imageName, tags := range cfg.Images {
|
||||
repoName := fmt.Sprintf("//%s", path.Join(registryName, imageName))
|
||||
logrus.WithFields(logrus.Fields{
|
||||
for imageName, refs := range cfg.Images {
|
||||
repoLogger := logrus.WithFields(logrus.Fields{
|
||||
"repo": imageName,
|
||||
"registry": registryName,
|
||||
}).Info("Processing repo")
|
||||
|
||||
serverCtx := &sourceCtx
|
||||
// override ctx with per-registryName options
|
||||
serverCtx.DockerCertPath = cfg.CertDir
|
||||
serverCtx.DockerDaemonCertPath = cfg.CertDir
|
||||
serverCtx.DockerDaemonInsecureSkipTLSVerify = (cfg.TLSVerify.skip == types.OptionalBoolTrue)
|
||||
serverCtx.DockerInsecureSkipTLSVerify = cfg.TLSVerify.skip
|
||||
serverCtx.DockerAuthConfig = &cfg.Credentials
|
||||
|
||||
var sourceReferences []types.ImageReference
|
||||
for _, tag := range tags {
|
||||
source := fmt.Sprintf("%s:%s", repoName, tag)
|
||||
|
||||
imageRef, err := docker.ParseReference(source)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"tag": source,
|
||||
}).Error("Error processing tag, skipping")
|
||||
logrus.Errorf("Error getting image reference: %s", err)
|
||||
continue
|
||||
}
|
||||
sourceReferences = append(sourceReferences, imageRef)
|
||||
})
|
||||
repoRef, err := parseRepositoryReference(fmt.Sprintf("%s/%s", registryName, imageName))
|
||||
if err != nil {
|
||||
repoLogger.Error("Error parsing repository name, skipping")
|
||||
logrus.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(tags) == 0 {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"repo": imageName,
|
||||
"registry": registryName,
|
||||
}).Info("Querying registry for image tags")
|
||||
repoLogger.Info("Processing repo")
|
||||
|
||||
imageRef, err := docker.ParseReference(repoName)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"repo": imageName,
|
||||
"registry": registryName,
|
||||
}).Error("Error processing repo, skipping")
|
||||
logrus.Error(err)
|
||||
continue
|
||||
var sourceReferences []types.ImageReference
|
||||
if len(refs) != 0 {
|
||||
for _, ref := range refs {
|
||||
tagLogger := logrus.WithFields(logrus.Fields{"ref": ref})
|
||||
var named reference.Named
|
||||
// first try as digest
|
||||
if d, err := digest.Parse(ref); err == nil {
|
||||
named, err = reference.WithDigest(repoRef, d)
|
||||
if err != nil {
|
||||
tagLogger.Error("Error processing ref, skipping")
|
||||
logrus.Error(err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
tagLogger.Debugf("Ref was not a digest, trying as a tag: %s", err)
|
||||
named, err = reference.WithTag(repoRef, ref)
|
||||
if err != nil {
|
||||
tagLogger.Error("Error parsing ref, skipping")
|
||||
logrus.Error(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
imageRef, err := docker.NewReference(named)
|
||||
if err != nil {
|
||||
tagLogger.Error("Error processing ref, skipping")
|
||||
logrus.Errorf("Error getting image reference: %s", err)
|
||||
continue
|
||||
}
|
||||
sourceReferences = append(sourceReferences, imageRef)
|
||||
}
|
||||
|
||||
sourceReferences, err = imagesToCopyFromRepo(imageRef, repoName, serverCtx)
|
||||
} else { // len(refs) == 0
|
||||
repoLogger.Info("Querying registry for image tags")
|
||||
sourceReferences, err = imagesToCopyFromRepo(serverCtx, repoRef)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"repo": imageName,
|
||||
"registry": registryName,
|
||||
}).Error("Error processing repo, skipping")
|
||||
repoLogger.Error("Error processing repo, skipping")
|
||||
logrus.Error(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(sourceReferences) == 0 {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"repo": imageName,
|
||||
"registry": registryName,
|
||||
}).Warnf("No tags to sync found")
|
||||
repoLogger.Warnf("No refs to sync found")
|
||||
continue
|
||||
}
|
||||
repoDescList = append(repoDescList, repoDescriptor{
|
||||
TaggedImages: sourceReferences,
|
||||
Context: serverCtx})
|
||||
ImageRefs: sourceReferences,
|
||||
Context: serverCtx})
|
||||
}
|
||||
|
||||
for imageName, tagRegex := range cfg.ImagesByTagRegex {
|
||||
repoLogger := logrus.WithFields(logrus.Fields{
|
||||
"repo": imageName,
|
||||
"registry": registryName,
|
||||
})
|
||||
repoRef, err := parseRepositoryReference(fmt.Sprintf("%s/%s", registryName, imageName))
|
||||
if err != nil {
|
||||
repoLogger.Error("Error parsing repository name, skipping")
|
||||
logrus.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
repoLogger.Info("Processing repo")
|
||||
|
||||
var sourceReferences []types.ImageReference
|
||||
|
||||
tagReg, err := regexp.Compile(tagRegex)
|
||||
if err != nil {
|
||||
repoLogger.WithFields(logrus.Fields{
|
||||
"regex": tagRegex,
|
||||
}).Error("Error parsing regex, skipping")
|
||||
logrus.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
repoLogger.Info("Querying registry for image tags")
|
||||
allSourceReferences, err := imagesToCopyFromRepo(serverCtx, repoRef)
|
||||
if err != nil {
|
||||
repoLogger.Error("Error processing repo, skipping")
|
||||
logrus.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
repoLogger.Infof("Start filtering using the regular expression: %v", tagRegex)
|
||||
for _, sReference := range allSourceReferences {
|
||||
tagged, isTagged := sReference.DockerReference().(reference.Tagged)
|
||||
if !isTagged {
|
||||
repoLogger.Errorf("Internal error, reference %s does not have a tag, skipping", sReference.DockerReference())
|
||||
continue
|
||||
}
|
||||
if tagReg.MatchString(tagged.Tag()) {
|
||||
sourceReferences = append(sourceReferences, sReference)
|
||||
}
|
||||
}
|
||||
|
||||
if len(sourceReferences) == 0 {
|
||||
repoLogger.Warnf("No refs to sync found")
|
||||
continue
|
||||
}
|
||||
repoDescList = append(repoDescList, repoDescriptor{
|
||||
ImageRefs: sourceReferences,
|
||||
Context: serverCtx})
|
||||
}
|
||||
|
||||
return repoDescList, nil
|
||||
@@ -366,32 +420,29 @@ func imagesToCopy(source string, transport string, sourceCtx *types.SystemContex
|
||||
desc := repoDescriptor{
|
||||
Context: sourceCtx,
|
||||
}
|
||||
refName := fmt.Sprintf("//%s", source)
|
||||
srcRef, err := docker.ParseReference(refName)
|
||||
named, err := reference.ParseNormalizedNamed(source) // May be a repository or an image.
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, fmt.Sprintf("Cannot obtain a valid image reference for transport %q and reference %q", docker.Transport.Name(), refName))
|
||||
return nil, errors.Wrapf(err, "Cannot obtain a valid image reference for transport %q and reference %q", docker.Transport.Name(), source)
|
||||
}
|
||||
imageTagged, err := isTagSpecified(source)
|
||||
if err != nil {
|
||||
return descriptors, err
|
||||
}
|
||||
|
||||
imageTagged := !reference.IsNameOnly(named)
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"imagename": source,
|
||||
"tagged": imageTagged,
|
||||
}).Info("Tag presence check")
|
||||
if imageTagged {
|
||||
desc.TaggedImages = append(desc.TaggedImages, srcRef)
|
||||
descriptors = append(descriptors, desc)
|
||||
break
|
||||
}
|
||||
|
||||
desc.TaggedImages, err = imagesToCopyFromRepo(
|
||||
srcRef,
|
||||
fmt.Sprintf("//%s", source),
|
||||
sourceCtx)
|
||||
|
||||
if err != nil {
|
||||
return descriptors, err
|
||||
}
|
||||
if len(desc.TaggedImages) == 0 {
|
||||
return descriptors, errors.Errorf("No images to sync found in %q", source)
|
||||
srcRef, err := docker.NewReference(named)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Cannot obtain a valid image reference for transport %q and reference %q", docker.Transport.Name(), named.String())
|
||||
}
|
||||
desc.ImageRefs = []types.ImageReference{srcRef}
|
||||
} else {
|
||||
desc.ImageRefs, err = imagesToCopyFromRepo(sourceCtx, named)
|
||||
if err != nil {
|
||||
return descriptors, err
|
||||
}
|
||||
if len(desc.ImageRefs) == 0 {
|
||||
return descriptors, errors.Errorf("No images to sync found in %q", source)
|
||||
}
|
||||
}
|
||||
descriptors = append(descriptors, desc)
|
||||
|
||||
@@ -405,11 +456,11 @@ func imagesToCopy(source string, transport string, sourceCtx *types.SystemContex
|
||||
}
|
||||
desc.DirBasePath = source
|
||||
var err error
|
||||
desc.TaggedImages, err = imagesToCopyFromDir(source)
|
||||
desc.ImageRefs, err = imagesToCopyFromDir(source)
|
||||
if err != nil {
|
||||
return descriptors, err
|
||||
}
|
||||
if len(desc.TaggedImages) == 0 {
|
||||
if len(desc.ImageRefs) == 0 {
|
||||
return descriptors, errors.Errorf("No images to sync found in %q", source)
|
||||
}
|
||||
descriptors = append(descriptors, desc)
|
||||
@@ -420,7 +471,7 @@ func imagesToCopy(source string, transport string, sourceCtx *types.SystemContex
|
||||
return descriptors, err
|
||||
}
|
||||
for registryName, registryConfig := range cfg {
|
||||
if len(registryConfig.Images) == 0 {
|
||||
if len(registryConfig.Images) == 0 && len(registryConfig.ImagesByTagRegex) == 0 {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"registry": registryName,
|
||||
}).Warn("No images specified for registry")
|
||||
@@ -477,14 +528,33 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) error {
|
||||
return errors.New("sync from 'dir' to 'dir' not implemented, consider using rsync instead")
|
||||
}
|
||||
|
||||
imageListSelection := copy.CopySystemImage
|
||||
if opts.all {
|
||||
imageListSelection = copy.CopyAllImages
|
||||
}
|
||||
|
||||
sourceCtx, err := opts.srcImage.newSystemContext()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var manifestType string
|
||||
if opts.format.present {
|
||||
manifestType, err = parseManifestFormat(opts.format.value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := opts.global.commandTimeoutContext()
|
||||
defer cancel()
|
||||
|
||||
sourceArg := args[0]
|
||||
srcRepoList, err := imagesToCopy(sourceArg, opts.source, sourceCtx)
|
||||
if err != nil {
|
||||
var srcRepoList []repoDescriptor
|
||||
if err = retry.RetryIfNecessary(ctx, func() error {
|
||||
srcRepoList, err = imagesToCopy(sourceArg, opts.source, sourceCtx)
|
||||
return err
|
||||
}, opts.retryOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -494,20 +564,20 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := opts.global.commandTimeoutContext()
|
||||
defer cancel()
|
||||
|
||||
imagesNumber := 0
|
||||
options := copy.Options{
|
||||
RemoveSignatures: opts.removeSignatures,
|
||||
SignBy: opts.signByFingerprint,
|
||||
ReportWriter: os.Stdout,
|
||||
DestinationCtx: destinationCtx,
|
||||
RemoveSignatures: opts.removeSignatures,
|
||||
SignBy: opts.signByFingerprint,
|
||||
ReportWriter: os.Stdout,
|
||||
DestinationCtx: destinationCtx,
|
||||
ImageListSelection: imageListSelection,
|
||||
OptimizeDestinationImageAlreadyExists: true,
|
||||
ForceManifestMIMEType: manifestType,
|
||||
}
|
||||
|
||||
for _, srcRepo := range srcRepoList {
|
||||
options.SourceCtx = srcRepo.Context
|
||||
for counter, ref := range srcRepo.TaggedImages {
|
||||
for counter, ref := range srcRepo.ImageRefs {
|
||||
var destSuffix string
|
||||
switch ref.Transport() {
|
||||
case docker.Transport:
|
||||
@@ -534,11 +604,13 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) error {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"from": transports.ImageName(ref),
|
||||
"to": transports.ImageName(destRef),
|
||||
}).Infof("Copying image tag %d/%d", counter+1, len(srcRepo.TaggedImages))
|
||||
}).Infof("Copying image ref %d/%d", counter+1, len(srcRepo.ImageRefs))
|
||||
|
||||
_, err = copy.Image(ctx, policyContext, destRef, ref, &options)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, fmt.Sprintf("Error copying tag %q", transports.ImageName(ref)))
|
||||
if err = retry.RetryIfNecessary(ctx, func() error {
|
||||
_, err = copy.Image(ctx, policyContext, destRef, ref, &options)
|
||||
return err
|
||||
}, opts.retryOpts); err != nil {
|
||||
return errors.Wrapf(err, "Error copying ref %q", transports.ImageName(ref))
|
||||
}
|
||||
imagesNumber++
|
||||
}
|
||||
|
||||
@@ -2,15 +2,20 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/common/pkg/retry"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/pkg/compression"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// errorShouldDisplayUsage is a subtype of error used by command handlers to indicate that cli.ShowSubcommandHelp should be called.
|
||||
@@ -18,16 +23,16 @@ type errorShouldDisplayUsage struct {
|
||||
error
|
||||
}
|
||||
|
||||
// commandAction intermediates between the cli.ActionFunc interface and the real handler,
|
||||
// primarily to ensure that cli.Context is not available to the handler, which in turn
|
||||
// makes sure that the cli.String() etc. flag access functions are not used,
|
||||
// and everything is done using the *Options structures and the Destination: members of cli.Flag.
|
||||
// handler may return errorShouldDisplayUsage to cause cli.ShowSubcommandHelp to be called.
|
||||
func commandAction(handler func(args []string, stdout io.Writer) error) cli.ActionFunc {
|
||||
return func(c *cli.Context) error {
|
||||
err := handler(([]string)(c.Args()), c.App.Writer)
|
||||
// commandAction intermediates between the RunE interface and the real handler,
|
||||
// primarily to ensure that cobra.Command is not available to the handler, which in turn
|
||||
// makes sure that the cmd.Flags() etc. flag access functions are not used,
|
||||
// and everything is done using the *Options structures and the *Var() methods of cmd.Flag().
|
||||
// handler may return errorShouldDisplayUsage to cause c.Help to be called.
|
||||
func commandAction(handler func(args []string, stdout io.Writer) error) func(cmd *cobra.Command, args []string) error {
|
||||
return func(c *cobra.Command, args []string) error {
|
||||
err := handler(args, c.OutOrStdout())
|
||||
if _, ok := err.(errorShouldDisplayUsage); ok {
|
||||
cli.ShowSubcommandHelp(c)
|
||||
c.Help()
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -39,20 +44,15 @@ type sharedImageOptions struct {
|
||||
authFilePath string // Path to a */containers/auth.json
|
||||
}
|
||||
|
||||
// imageFlags prepares a collection of CLI flags writing into sharedImageOptions, and the managed sharedImageOptions structure.
|
||||
func sharedImageFlags() ([]cli.Flag, *sharedImageOptions) {
|
||||
// sharedImageFlags prepares a collection of CLI flags writing into sharedImageOptions, and the managed sharedImageOptions structure.
|
||||
func sharedImageFlags() (pflag.FlagSet, *sharedImageOptions) {
|
||||
opts := sharedImageOptions{}
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "authfile",
|
||||
Usage: "path of the authentication file. Example: ${XDG_RUNTIME_DIR}/containers/auth.json",
|
||||
Value: os.Getenv("REGISTRY_AUTH_FILE"),
|
||||
Destination: &opts.authFilePath,
|
||||
},
|
||||
}, &opts
|
||||
fs := pflag.FlagSet{}
|
||||
fs.StringVar(&opts.authFilePath, "authfile", os.Getenv("REGISTRY_AUTH_FILE"), "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json")
|
||||
return fs, &opts
|
||||
}
|
||||
|
||||
// imageOptions collects CLI flags specific to the "docker" transport, which are
|
||||
// dockerImageOptions collects CLI flags specific to the "docker" transport, which are
|
||||
// the same across subcommands, but may be different for each image
|
||||
// (e.g. may differ between the source and destination of a copy)
|
||||
type dockerImageOptions struct {
|
||||
@@ -60,6 +60,7 @@ type dockerImageOptions struct {
|
||||
shared *sharedImageOptions // May be shared across several imageOptions instances.
|
||||
authFilePath optionalString // Path to a */containers/auth.json (prefixed version to override shared image option).
|
||||
credsOption optionalString // username[:password] for accessing a registry
|
||||
registryToken optionalString // token to be used directly as a Bearer token when accessing the registry
|
||||
dockerCertPath string // A directory using Docker-like *.{crt,cert,key} files for connecting to a registry or a daemon
|
||||
tlsVerify optionalBool // Require HTTPS and verify certificates (for docker: and docker-daemon:)
|
||||
noCreds bool // Access the registry anonymously
|
||||
@@ -75,101 +76,72 @@ type imageOptions struct {
|
||||
|
||||
// dockerImageFlags prepares a collection of docker-transport specific CLI flags
|
||||
// writing into imageOptions, and the managed imageOptions structure.
|
||||
func dockerImageFlags(global *globalOptions, shared *sharedImageOptions, flagPrefix, credsOptionAlias string) ([]cli.Flag, *imageOptions) {
|
||||
opts := imageOptions{
|
||||
func dockerImageFlags(global *globalOptions, shared *sharedImageOptions, flagPrefix, credsOptionAlias string) (pflag.FlagSet, *imageOptions) {
|
||||
flags := imageOptions{
|
||||
dockerImageOptions: dockerImageOptions{
|
||||
global: global,
|
||||
shared: shared,
|
||||
},
|
||||
}
|
||||
|
||||
// This is horribly ugly, but we need to support the old option forms of (skopeo copy) for compatibility.
|
||||
// Don't add any more cases like this.
|
||||
credsOptionExtra := ""
|
||||
if credsOptionAlias != "" {
|
||||
credsOptionExtra += "," + credsOptionAlias
|
||||
}
|
||||
|
||||
var flags []cli.Flag
|
||||
fs := pflag.FlagSet{}
|
||||
if flagPrefix != "" {
|
||||
// the non-prefixed flag is handled by a shared flag.
|
||||
flags = append(flags,
|
||||
cli.GenericFlag{
|
||||
Name: flagPrefix + "authfile",
|
||||
Usage: "path of the authentication file. Example: ${XDG_RUNTIME_DIR}/containers/auth.json",
|
||||
Value: newOptionalStringValue(&opts.authFilePath),
|
||||
},
|
||||
)
|
||||
fs.Var(newOptionalStringValue(&flags.authFilePath), flagPrefix+"authfile", "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json")
|
||||
}
|
||||
flags = append(flags,
|
||||
cli.GenericFlag{
|
||||
Name: flagPrefix + "creds" + credsOptionExtra,
|
||||
Usage: "Use `USERNAME[:PASSWORD]` for accessing the registry",
|
||||
Value: newOptionalStringValue(&opts.credsOption),
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: flagPrefix + "cert-dir",
|
||||
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the registry or daemon",
|
||||
Destination: &opts.dockerCertPath,
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: flagPrefix + "tls-verify",
|
||||
Usage: "require HTTPS and verify certificates when talking to the container registry or daemon (defaults to true)",
|
||||
Value: newOptionalBoolValue(&opts.tlsVerify),
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: flagPrefix + "no-creds",
|
||||
Usage: "Access the registry anonymously",
|
||||
Destination: &opts.noCreds,
|
||||
},
|
||||
)
|
||||
return flags, &opts
|
||||
fs.Var(newOptionalStringValue(&flags.credsOption), flagPrefix+"creds", "Use `USERNAME[:PASSWORD]` for accessing the registry")
|
||||
if credsOptionAlias != "" {
|
||||
// This is horribly ugly, but we need to support the old option forms of (skopeo copy) for compatibility.
|
||||
// Don't add any more cases like this.
|
||||
f := fs.VarPF(newOptionalStringValue(&flags.credsOption), credsOptionAlias, "", "Use `USERNAME[:PASSWORD]` for accessing the registry")
|
||||
f.Hidden = true
|
||||
}
|
||||
fs.Var(newOptionalStringValue(&flags.registryToken), flagPrefix+"registry-token", "Provide a Bearer token for accessing the registry")
|
||||
fs.StringVar(&flags.dockerCertPath, flagPrefix+"cert-dir", "", "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the registry or daemon")
|
||||
optionalBoolFlag(&fs, &flags.tlsVerify, flagPrefix+"tls-verify", "require HTTPS and verify certificates when talking to the container registry or daemon (defaults to true)")
|
||||
fs.BoolVar(&flags.noCreds, flagPrefix+"no-creds", false, "Access the registry anonymously")
|
||||
return fs, &flags
|
||||
}
|
||||
|
||||
// imageFlags prepares a collection of CLI flags writing into imageOptions, and the managed imageOptions structure.
|
||||
func imageFlags(global *globalOptions, shared *sharedImageOptions, flagPrefix, credsOptionAlias string) ([]cli.Flag, *imageOptions) {
|
||||
func imageFlags(global *globalOptions, shared *sharedImageOptions, flagPrefix, credsOptionAlias string) (pflag.FlagSet, *imageOptions) {
|
||||
dockerFlags, opts := dockerImageFlags(global, shared, flagPrefix, credsOptionAlias)
|
||||
|
||||
return append(dockerFlags, []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: flagPrefix + "shared-blob-dir",
|
||||
Usage: "`DIRECTORY` to use to share blobs across OCI repositories",
|
||||
Destination: &opts.sharedBlobDir,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: flagPrefix + "daemon-host",
|
||||
Usage: "use docker daemon host at `HOST` (docker-daemon: only)",
|
||||
Destination: &opts.dockerDaemonHost,
|
||||
},
|
||||
}...), opts
|
||||
fs := pflag.FlagSet{}
|
||||
fs.StringVar(&opts.sharedBlobDir, flagPrefix+"shared-blob-dir", "", "`DIRECTORY` to use to share blobs across OCI repositories")
|
||||
fs.StringVar(&opts.dockerDaemonHost, flagPrefix+"daemon-host", "", "use docker daemon host at `HOST` (docker-daemon: only)")
|
||||
fs.AddFlagSet(&dockerFlags)
|
||||
return fs, opts
|
||||
}
|
||||
|
||||
type retryOptions struct {
|
||||
maxRetry int // The number of times to possibly retry
|
||||
}
|
||||
|
||||
func retryFlags() (pflag.FlagSet, *retry.RetryOptions) {
|
||||
opts := retry.RetryOptions{}
|
||||
fs := pflag.FlagSet{}
|
||||
fs.IntVar(&opts.MaxRetry, "retry-times", 0, "the number of times to possibly retry")
|
||||
return fs, &opts
|
||||
}
|
||||
|
||||
// newSystemContext returns a *types.SystemContext corresponding to opts.
|
||||
// It is guaranteed to return a fresh instance, so it is safe to make additional updates to it.
|
||||
func (opts *imageOptions) newSystemContext() (*types.SystemContext, error) {
|
||||
ctx := &types.SystemContext{
|
||||
RegistriesDirPath: opts.global.registriesDirPath,
|
||||
ArchitectureChoice: opts.global.overrideArch,
|
||||
OSChoice: opts.global.overrideOS,
|
||||
VariantChoice: opts.global.overrideVariant,
|
||||
DockerCertPath: opts.dockerCertPath,
|
||||
OCISharedBlobDirPath: opts.sharedBlobDir,
|
||||
AuthFilePath: opts.shared.authFilePath,
|
||||
DockerDaemonHost: opts.dockerDaemonHost,
|
||||
DockerDaemonCertPath: opts.dockerCertPath,
|
||||
SystemRegistriesConfPath: opts.global.registriesConfPath,
|
||||
BigFilesTemporaryDir: opts.global.tmpDir,
|
||||
}
|
||||
// *types.SystemContext instance from globalOptions
|
||||
// imageOptions option overrides the instance if both are present.
|
||||
ctx := opts.global.newSystemContext()
|
||||
ctx.DockerCertPath = opts.dockerCertPath
|
||||
ctx.OCISharedBlobDirPath = opts.sharedBlobDir
|
||||
ctx.AuthFilePath = opts.shared.authFilePath
|
||||
ctx.DockerDaemonHost = opts.dockerDaemonHost
|
||||
ctx.DockerDaemonCertPath = opts.dockerCertPath
|
||||
if opts.dockerImageOptions.authFilePath.present {
|
||||
ctx.AuthFilePath = opts.dockerImageOptions.authFilePath.value
|
||||
}
|
||||
if opts.tlsVerify.present {
|
||||
ctx.DockerDaemonInsecureSkipTLSVerify = !opts.tlsVerify.value
|
||||
}
|
||||
// DEPRECATED: We support this for backward compatibility, but override it if a per-image flag is provided.
|
||||
if opts.global.tlsVerify.present {
|
||||
ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.global.tlsVerify.value)
|
||||
}
|
||||
if opts.tlsVerify.present {
|
||||
ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.tlsVerify.value)
|
||||
}
|
||||
@@ -183,6 +155,9 @@ func (opts *imageOptions) newSystemContext() (*types.SystemContext, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if opts.registryToken.present {
|
||||
ctx.DockerBearerRegistryToken = opts.registryToken.value
|
||||
}
|
||||
if opts.noCreds {
|
||||
ctx.DockerAuthConfig = &types.DockerAuthConfig{}
|
||||
}
|
||||
@@ -190,7 +165,7 @@ func (opts *imageOptions) newSystemContext() (*types.SystemContext, error) {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// imageDestOptions is a superset of imageOptions specialized for iamge destinations.
|
||||
// imageDestOptions is a superset of imageOptions specialized for image destinations.
|
||||
type imageDestOptions struct {
|
||||
*imageOptions
|
||||
dirForceCompression bool // Compress layers when saving to the dir: transport
|
||||
@@ -200,32 +175,16 @@ type imageDestOptions struct {
|
||||
}
|
||||
|
||||
// imageDestFlags prepares a collection of CLI flags writing into imageDestOptions, and the managed imageDestOptions structure.
|
||||
func imageDestFlags(global *globalOptions, shared *sharedImageOptions, flagPrefix, credsOptionAlias string) ([]cli.Flag, *imageDestOptions) {
|
||||
func imageDestFlags(global *globalOptions, shared *sharedImageOptions, flagPrefix, credsOptionAlias string) (pflag.FlagSet, *imageDestOptions) {
|
||||
genericFlags, genericOptions := imageFlags(global, shared, flagPrefix, credsOptionAlias)
|
||||
opts := imageDestOptions{imageOptions: genericOptions}
|
||||
|
||||
return append(genericFlags, []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: flagPrefix + "compress",
|
||||
Usage: "Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source)",
|
||||
Destination: &opts.dirForceCompression,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: flagPrefix + "oci-accept-uncompressed-layers",
|
||||
Usage: "Allow uncompressed image layers when saving to an OCI image using the 'oci' transport. (default is to compress things that aren't compressed)",
|
||||
Destination: &opts.ociAcceptUncompressedLayers,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: flagPrefix + "compress-format",
|
||||
Usage: "`FORMAT` to use for the compression",
|
||||
Destination: &opts.compressionFormat,
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: flagPrefix + "compress-level",
|
||||
Usage: "`LEVEL` to use for the compression",
|
||||
Value: newOptionalIntValue(&opts.compressionLevel),
|
||||
},
|
||||
}...), &opts
|
||||
fs := pflag.FlagSet{}
|
||||
fs.AddFlagSet(&genericFlags)
|
||||
fs.BoolVar(&opts.dirForceCompression, flagPrefix+"compress", false, "Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source)")
|
||||
fs.BoolVar(&opts.ociAcceptUncompressedLayers, flagPrefix+"oci-accept-uncompressed-layers", false, "Allow uncompressed image layers when saving to an OCI image using the 'oci' transport. (default is to compress things that aren't compressed)")
|
||||
fs.StringVar(&opts.compressionFormat, flagPrefix+"compress-format", "", "`FORMAT` to use for the compression")
|
||||
fs.Var(newOptionalIntValue(&opts.compressionLevel), flagPrefix+"compress-level", "`LEVEL` to use for the compression")
|
||||
return fs, &opts
|
||||
}
|
||||
|
||||
// newSystemContext returns a *types.SystemContext corresponding to opts.
|
||||
@@ -276,20 +235,6 @@ func getDockerAuth(creds string) (*types.DockerAuthConfig, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseImage converts image URL-like string to an initialized handler for that image.
|
||||
// The caller must call .Close() on the returned ImageCloser.
|
||||
func parseImage(ctx context.Context, opts *imageOptions, name string) (types.ImageCloser, error) {
|
||||
ref, err := alltransports.ParseImageName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sys, err := opts.newSystemContext()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ref.NewImage(ctx, sys)
|
||||
}
|
||||
|
||||
// parseImageSource converts image URL-like string to an ImageSource.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func parseImageSource(ctx context.Context, opts *imageOptions, name string) (types.ImageSource, error) {
|
||||
@@ -303,3 +248,47 @@ func parseImageSource(ctx context.Context, opts *imageOptions, name string) (typ
|
||||
}
|
||||
return ref.NewImageSource(ctx, sys)
|
||||
}
|
||||
|
||||
// parseManifestFormat parses format parameter for copy and sync command.
|
||||
// It returns string value to use as manifest MIME type
|
||||
func parseManifestFormat(manifestFormat string) (string, error) {
|
||||
switch manifestFormat {
|
||||
case "oci":
|
||||
return imgspecv1.MediaTypeImageManifest, nil
|
||||
case "v2s1":
|
||||
return manifest.DockerV2Schema1SignedMediaType, nil
|
||||
case "v2s2":
|
||||
return manifest.DockerV2Schema2MediaType, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown format %q. Choose one of the supported formats: 'oci', 'v2s1', or 'v2s2'", manifestFormat)
|
||||
}
|
||||
}
|
||||
|
||||
// usageTemplate returns the usage template for skopeo commands
|
||||
// This blocks the displaying of the global options. The main skopeo
|
||||
// command should not use this.
|
||||
const usageTemplate = `Usage:{{if .Runnable}}
|
||||
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
|
||||
|
||||
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
|
||||
|
||||
Aliases:
|
||||
{{.NameAndAliases}}{{end}}{{if .HasExample}}
|
||||
|
||||
Examples:
|
||||
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
|
||||
|
||||
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
|
||||
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
|
||||
|
||||
Flags:
|
||||
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
// adjustUsage uses usageTemplate template to get rid the GlobalOption from usage
|
||||
// and disable [flag] at the end of command usage
|
||||
func adjustUsage(c *cobra.Command) {
|
||||
c.SetUsageTemplate(usageTemplate)
|
||||
c.DisableFlagsInUseLine = true
|
||||
}
|
||||
|
||||
@@ -1,42 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// fakeGlobalOptions creates globalOptions and sets it according to flags.
|
||||
// NOTE: This is QUITE FAKE; none of the urfave/cli normalization and the like happens.
|
||||
func fakeGlobalOptions(t *testing.T, flags []string) *globalOptions {
|
||||
func fakeGlobalOptions(t *testing.T, flags []string) (*globalOptions, *cobra.Command) {
|
||||
app, opts := createApp()
|
||||
|
||||
flagSet := flag.NewFlagSet(app.Name, flag.ContinueOnError)
|
||||
for _, f := range app.Flags {
|
||||
f.Apply(flagSet)
|
||||
}
|
||||
err := flagSet.Parse(flags)
|
||||
cmd := &cobra.Command{}
|
||||
app.AddCommand(cmd)
|
||||
err := cmd.ParseFlags(flags)
|
||||
require.NoError(t, err)
|
||||
|
||||
return opts
|
||||
return opts, cmd
|
||||
}
|
||||
|
||||
// fakeImageOptions creates imageOptions and sets it according to globalFlags/cmdFlags.
|
||||
// NOTE: This is QUITE FAKE; none of the urfave/cli normalization and the like happens.
|
||||
func fakeImageOptions(t *testing.T, flagPrefix string, globalFlags []string, cmdFlags []string) *imageOptions {
|
||||
globalOpts := fakeGlobalOptions(t, globalFlags)
|
||||
|
||||
globalOpts, cmd := fakeGlobalOptions(t, globalFlags)
|
||||
sharedFlags, sharedOpts := sharedImageFlags()
|
||||
imageFlags, imageOpts := imageFlags(globalOpts, sharedOpts, flagPrefix, "")
|
||||
flagSet := flag.NewFlagSet("fakeImageOptions", flag.ContinueOnError)
|
||||
for _, f := range append(sharedFlags, imageFlags...) {
|
||||
f.Apply(flagSet)
|
||||
}
|
||||
err := flagSet.Parse(cmdFlags)
|
||||
cmd.Flags().AddFlagSet(&sharedFlags)
|
||||
cmd.Flags().AddFlagSet(&imageFlags)
|
||||
err := cmd.ParseFlags(cmdFlags)
|
||||
require.NoError(t, err)
|
||||
return imageOpts
|
||||
}
|
||||
@@ -46,7 +39,9 @@ func TestImageOptionsNewSystemContext(t *testing.T) {
|
||||
opts := fakeImageOptions(t, "dest-", []string{}, []string{})
|
||||
res, err := opts.newSystemContext()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &types.SystemContext{}, res)
|
||||
assert.Equal(t, &types.SystemContext{
|
||||
DockerRegistryUserAgent: defaultUserAgent,
|
||||
}, res)
|
||||
|
||||
// Set everything to non-default values.
|
||||
opts = fakeImageOptions(t, "dest-", []string{
|
||||
@@ -63,6 +58,7 @@ func TestImageOptionsNewSystemContext(t *testing.T) {
|
||||
"--dest-daemon-host", "daemon-host.example.com",
|
||||
"--dest-tls-verify=false",
|
||||
"--dest-creds", "creds-user:creds-password",
|
||||
"--dest-registry-token", "faketoken",
|
||||
})
|
||||
res, err = opts.newSystemContext()
|
||||
require.NoError(t, err)
|
||||
@@ -76,9 +72,11 @@ func TestImageOptionsNewSystemContext(t *testing.T) {
|
||||
DockerCertPath: "/srv/cert-dir",
|
||||
DockerInsecureSkipTLSVerify: types.OptionalBoolTrue,
|
||||
DockerAuthConfig: &types.DockerAuthConfig{Username: "creds-user", Password: "creds-password"},
|
||||
DockerBearerRegistryToken: "faketoken",
|
||||
DockerDaemonCertPath: "/srv/cert-dir",
|
||||
DockerDaemonHost: "daemon-host.example.com",
|
||||
DockerDaemonInsecureSkipTLSVerify: true,
|
||||
DockerRegistryUserAgent: defaultUserAgent,
|
||||
BigFilesTemporaryDir: "/srv",
|
||||
}, res)
|
||||
|
||||
@@ -120,17 +118,13 @@ func TestImageOptionsNewSystemContext(t *testing.T) {
|
||||
}
|
||||
|
||||
// fakeImageDestOptions creates imageDestOptions and sets it according to globalFlags/cmdFlags.
|
||||
// NOTE: This is QUITE FAKE; none of the urfave/cli normalization and the like happens.
|
||||
func fakeImageDestOptions(t *testing.T, flagPrefix string, globalFlags []string, cmdFlags []string) *imageDestOptions {
|
||||
globalOpts := fakeGlobalOptions(t, globalFlags)
|
||||
|
||||
globalOpts, cmd := fakeGlobalOptions(t, globalFlags)
|
||||
sharedFlags, sharedOpts := sharedImageFlags()
|
||||
imageFlags, imageOpts := imageDestFlags(globalOpts, sharedOpts, flagPrefix, "")
|
||||
flagSet := flag.NewFlagSet("fakeImageDestOptions", flag.ContinueOnError)
|
||||
for _, f := range append(sharedFlags, imageFlags...) {
|
||||
f.Apply(flagSet)
|
||||
}
|
||||
err := flagSet.Parse(cmdFlags)
|
||||
cmd.Flags().AddFlagSet(&sharedFlags)
|
||||
cmd.Flags().AddFlagSet(&imageFlags)
|
||||
err := cmd.ParseFlags(cmdFlags)
|
||||
require.NoError(t, err)
|
||||
return imageOpts
|
||||
}
|
||||
@@ -140,7 +134,9 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) {
|
||||
opts := fakeImageDestOptions(t, "dest-", []string{}, []string{})
|
||||
res, err := opts.newSystemContext()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &types.SystemContext{}, res)
|
||||
assert.Equal(t, &types.SystemContext{
|
||||
DockerRegistryUserAgent: defaultUserAgent,
|
||||
}, res)
|
||||
|
||||
oldXRD, hasXRD := os.LookupEnv("REGISTRY_AUTH_FILE")
|
||||
defer func() {
|
||||
@@ -160,7 +156,10 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) {
|
||||
})
|
||||
res, err = opts.newSystemContext()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &types.SystemContext{AuthFilePath: authFile}, res)
|
||||
assert.Equal(t, &types.SystemContext{
|
||||
AuthFilePath: authFile,
|
||||
DockerRegistryUserAgent: defaultUserAgent,
|
||||
}, res)
|
||||
|
||||
// Set everything to non-default values.
|
||||
opts = fakeImageDestOptions(t, "dest-", []string{
|
||||
@@ -177,6 +176,7 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) {
|
||||
"--dest-daemon-host", "daemon-host.example.com",
|
||||
"--dest-tls-verify=false",
|
||||
"--dest-creds", "creds-user:creds-password",
|
||||
"--dest-registry-token", "faketoken",
|
||||
})
|
||||
res, err = opts.newSystemContext()
|
||||
require.NoError(t, err)
|
||||
@@ -190,9 +190,11 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) {
|
||||
DockerCertPath: "/srv/cert-dir",
|
||||
DockerInsecureSkipTLSVerify: types.OptionalBoolTrue,
|
||||
DockerAuthConfig: &types.DockerAuthConfig{Username: "creds-user", Password: "creds-password"},
|
||||
DockerBearerRegistryToken: "faketoken",
|
||||
DockerDaemonCertPath: "/srv/cert-dir",
|
||||
DockerDaemonHost: "daemon-host.example.com",
|
||||
DockerDaemonInsecureSkipTLSVerify: true,
|
||||
DockerRegistryUserAgent: defaultUserAgent,
|
||||
DirForceCompress: true,
|
||||
BigFilesTemporaryDir: "/srv",
|
||||
}, res)
|
||||
@@ -203,6 +205,38 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestParseManifestFormat(t *testing.T) {
|
||||
for _, testCase := range []struct {
|
||||
formatParam string
|
||||
expectedManifestType string
|
||||
expectErr bool
|
||||
}{
|
||||
{"oci",
|
||||
imgspecv1.MediaTypeImageManifest,
|
||||
false},
|
||||
{"v2s1",
|
||||
manifest.DockerV2Schema1SignedMediaType,
|
||||
false},
|
||||
{"v2s2",
|
||||
manifest.DockerV2Schema2MediaType,
|
||||
false},
|
||||
{"",
|
||||
"",
|
||||
true},
|
||||
{"badValue",
|
||||
"",
|
||||
true},
|
||||
} {
|
||||
manifestType, err := parseManifestFormat(testCase.formatParam)
|
||||
if testCase.expectErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, manifestType, testCase.expectedManifestType)
|
||||
}
|
||||
}
|
||||
|
||||
// since there is a shared authfile image option and a non-shared (prefixed) one, make sure the override logic
|
||||
// works correctly.
|
||||
func TestImageOptionsAuthfileOverride(t *testing.T) {
|
||||
@@ -242,7 +276,8 @@ func TestImageOptionsAuthfileOverride(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, &types.SystemContext{
|
||||
AuthFilePath: testCase.expectedAuthfilePath,
|
||||
AuthFilePath: testCase.expectedAuthfilePath,
|
||||
DockerRegistryUserAgent: defaultUserAgent,
|
||||
}, res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@ _skopeo_copy() {
|
||||
--dest-tls-verify
|
||||
--src-daemon-host
|
||||
--dest-daemon-host
|
||||
--src-registry-token
|
||||
--dest-registry-token
|
||||
"
|
||||
|
||||
local boolean_options="
|
||||
@@ -68,11 +70,50 @@ _skopeo_copy() {
|
||||
_complete_ "$options_with_args" "$boolean_options" "$transports"
|
||||
}
|
||||
|
||||
_skopeo_sync() {
|
||||
local options_with_args="
|
||||
--authfile
|
||||
--dest
|
||||
--dest-authfile
|
||||
--dest-cert-
|
||||
--dest-creds
|
||||
--dest-registry-token string
|
||||
--format
|
||||
--retry-times
|
||||
--sign-by
|
||||
--src
|
||||
--src-authfile
|
||||
--src-cert-dir
|
||||
--src-creds
|
||||
--src-registry-token
|
||||
"
|
||||
|
||||
local boolean_options="
|
||||
--all
|
||||
--dest-no-creds
|
||||
--dest-tls-verify
|
||||
--remove-signatures
|
||||
--scoped
|
||||
--src-no-creds
|
||||
--src-tls-verify
|
||||
"
|
||||
|
||||
local transports
|
||||
transports="
|
||||
$(_skopeo_supported_transports "${FUNCNAME//"_skopeo_"/}")
|
||||
"
|
||||
|
||||
_complete_ "$options_with_args" "$boolean_options" "$transports"
|
||||
}
|
||||
|
||||
_skopeo_inspect() {
|
||||
local options_with_args="
|
||||
--authfile
|
||||
--creds
|
||||
--cert-dir
|
||||
--format
|
||||
--retry-times
|
||||
--registry-token
|
||||
"
|
||||
local boolean_options="
|
||||
--config
|
||||
@@ -119,6 +160,7 @@ _skopeo_delete() {
|
||||
--authfile
|
||||
--creds
|
||||
--cert-dir
|
||||
--registry-token
|
||||
"
|
||||
local boolean_options="
|
||||
--tls-verify
|
||||
@@ -135,11 +177,14 @@ _skopeo_delete() {
|
||||
|
||||
_skopeo_layers() {
|
||||
local options_with_args="
|
||||
--authfile
|
||||
--creds
|
||||
--cert-dir
|
||||
--registry-token
|
||||
"
|
||||
local boolean_options="
|
||||
--tls-verify
|
||||
--no-creds
|
||||
"
|
||||
_complete_ "$options_with_args" "$boolean_options"
|
||||
}
|
||||
@@ -149,6 +194,7 @@ _skopeo_list_repository_tags() {
|
||||
--authfile
|
||||
--creds
|
||||
--cert-dir
|
||||
--registry-token
|
||||
"
|
||||
|
||||
local boolean_options="
|
||||
@@ -158,8 +204,35 @@ _skopeo_list_repository_tags() {
|
||||
_complete_ "$options_with_args" "$boolean_options"
|
||||
}
|
||||
|
||||
_skopeo_login() {
|
||||
local options_with_args="
|
||||
--authfile
|
||||
--cert-dir
|
||||
--password -p
|
||||
--username -u
|
||||
"
|
||||
|
||||
local boolean_options="
|
||||
--get-login
|
||||
--tls-verify
|
||||
--password-stdin
|
||||
"
|
||||
_complete_ "$options_with_args" "$boolean_options"
|
||||
}
|
||||
|
||||
_skopeo_logout() {
|
||||
local options_with_args="
|
||||
--authfile
|
||||
"
|
||||
|
||||
local boolean_options="
|
||||
--all -a
|
||||
"
|
||||
_complete_ "$options_with_args" "$boolean_options"
|
||||
}
|
||||
|
||||
_skopeo_skopeo() {
|
||||
# XXX: Changes here need to be refleceted in the manually expanded
|
||||
# XXX: Changes here need to be reflected in the manually expanded
|
||||
# string in the `case` statement below as well.
|
||||
local options_with_args="
|
||||
--policy
|
||||
@@ -177,8 +250,23 @@ _skopeo_skopeo() {
|
||||
--help -h
|
||||
"
|
||||
|
||||
local commands=(
|
||||
copy
|
||||
delete
|
||||
inspect
|
||||
list-tags
|
||||
login
|
||||
logout
|
||||
manifest-digest
|
||||
standalone-sign
|
||||
standalone-verify
|
||||
sync
|
||||
help
|
||||
h
|
||||
)
|
||||
|
||||
case "$prev" in
|
||||
# XXX: Changes here need to be refleceted in $options_with_args as well.
|
||||
# XXX: Changes here need to be reflected in $options_with_args as well.
|
||||
--policy|--registries.d|--override-arch|--override-os|--override-variant|--command-timeout)
|
||||
return
|
||||
;;
|
||||
@@ -189,8 +277,6 @@ _skopeo_skopeo() {
|
||||
while IFS='' read -r line; do COMPREPLY+=("$line"); done < <(compgen -W "$boolean_options $options_with_args" -- "$cur")
|
||||
;;
|
||||
*)
|
||||
commands=$( "${COMP_WORDS[@]:0:$COMP_CWORD}" --generate-bash-completion )
|
||||
|
||||
while IFS='' read -r line; do COMPREPLY+=("$line"); done < <(compgen -W "${commands[*]} help" -- "$cur")
|
||||
;;
|
||||
esac
|
||||
@@ -210,7 +296,7 @@ _cli_bash_autocomplete() {
|
||||
local counter=1
|
||||
while [ $counter -lt "$cword" ]; do
|
||||
case "${words[$counter]}" in
|
||||
skopeo|copy|inspect|delete|manifest-digest|standalone-sign|standalone-verify|help|h|list-repository-tags)
|
||||
skopeo|copy|sync|inspect|delete|manifest-digest|standalone-sign|standalone-verify|help|h|list-repository-tags)
|
||||
command="${words[$counter]//-/_}"
|
||||
cpos=$counter
|
||||
(( cpos++ ))
|
||||
|
||||
102
contrib/cirrus/runner.sh
Executable file
102
contrib/cirrus/runner.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is intended to be executed by automation or humans
|
||||
# under a hack/get_ci_vm.sh context. Use under any other circumstances
|
||||
# is unlikely to function.
|
||||
|
||||
set -e
|
||||
|
||||
_EOL=20250501
|
||||
if [[ $(date +%Y%m%d) -ge $_EOL ]]; then
|
||||
die "As of $_EOL this branch is probably
|
||||
no longer supported in RHEL 8.4, please
|
||||
confirm this with RHEL Program Management. If so:
|
||||
It should be removed from Cirrus-Cron,
|
||||
the .cirrus.yml file removed, and
|
||||
the VM images (manually) unmarked
|
||||
'permanent=true'"
|
||||
fi
|
||||
|
||||
if [[ -r "/etc/automation_environment" ]]; then
|
||||
source /etc/automation_environment
|
||||
source $AUTOMATION_LIB_PATH/common_lib.sh
|
||||
else
|
||||
(
|
||||
echo "WARNING: It does not appear that containers/automation was installed."
|
||||
echo " Functionality of most of ${BASH_SOURCE[0]} will be negatively"
|
||||
echo " impacted."
|
||||
) > /dev/stderr
|
||||
fi
|
||||
|
||||
OS_RELEASE_ID="$(source /etc/os-release; echo $ID)"
|
||||
# GCE image-name compatible string representation of distribution _major_ version
|
||||
OS_RELEASE_VER="$(source /etc/os-release; echo $VERSION_ID | tr -d '.')"
|
||||
# Combined to ease some usage
|
||||
OS_REL_VER="${OS_RELEASE_ID}-${OS_RELEASE_VER}"
|
||||
|
||||
export "PATH=$PATH:$GOPATH/bin"
|
||||
|
||||
podmanmake() {
|
||||
req_env_vars GOPATH SKOPEO_PATH SKOPEO_CI_CONTAINER_FQIN
|
||||
warn "Accumulated technical-debt requires execution inside a --privileged container. This is very likely hiding bugs!"
|
||||
showrun podman run -it --rm --privileged \
|
||||
-e GOPATH=$GOPATH \
|
||||
-v $GOPATH:$GOPATH:Z \
|
||||
-w $SKOPEO_PATH \
|
||||
$SKOPEO_CI_CONTAINER_FQIN \
|
||||
make "$@"
|
||||
}
|
||||
|
||||
_run_setup() {
|
||||
if [[ "$OS_RELEASE_ID" == "fedora" ]]; then
|
||||
# This is required as part of the standard Fedora VM setup
|
||||
growpart /dev/sda 1
|
||||
resize2fs /dev/sda1
|
||||
|
||||
# VM's come with the distro. skopeo pre-installed
|
||||
dnf erase -y skopeo
|
||||
else
|
||||
die "Unknown/unsupported distro. $OS_REL_VER"
|
||||
fi
|
||||
}
|
||||
|
||||
_run_vendor() {
|
||||
podmanmake vendor BUILDTAGS="$BUILDTAGS"
|
||||
}
|
||||
|
||||
_run_build() {
|
||||
podmanmake bin/skopeo BUILDTAGS="$BUILDTAGS"
|
||||
}
|
||||
|
||||
_run_validate() {
|
||||
podmanmake validate-local BUILDTAGS="$BUILDTAGS"
|
||||
}
|
||||
|
||||
_run_unit() {
|
||||
podmanmake test-integration-local BUILDTAGS="$BUILDTAGS"
|
||||
}
|
||||
|
||||
_run_integration() {
|
||||
podmanmake test-integration-local BUILDTAGS="$BUILDTAGS"
|
||||
}
|
||||
|
||||
_run_system() {
|
||||
# Ensure we start with a clean-slate
|
||||
podman system reset --force
|
||||
# Executes with containers required for testing.
|
||||
showrun make test-system-local BUILDTAGS="$BUILDTAGS"
|
||||
}
|
||||
|
||||
req_env_vars SKOPEO_PATH BUILDTAGS
|
||||
|
||||
handler="_run_${1}"
|
||||
if [ "$(type -t $handler)" != "function" ]; then
|
||||
die "Unknown/Unsupported command-line argument '$1'"
|
||||
fi
|
||||
|
||||
msg "************************************************************"
|
||||
msg "Runner executing $1 on $OS_REL_VER"
|
||||
msg "************************************************************"
|
||||
|
||||
cd "$SKOPEO_PATH"
|
||||
$handler
|
||||
43
contrib/skopeoimage/README.md
Normal file
43
contrib/skopeoimage/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
<img src="https://cdn.rawgit.com/containers/skopeo/master/docs/skopeo.svg" width="250">
|
||||
|
||||
----
|
||||
|
||||
# skopeoimage
|
||||
|
||||
## Overview
|
||||
|
||||
This directory contains the Dockerfiles necessary to create the three skopeoimage container
|
||||
images that are housed on quay.io under the skopeo account. All three repositories where
|
||||
the images live are public and can be pulled without credentials. These container images
|
||||
are secured and the resulting containers can run safely. The container images are built
|
||||
using the latest Fedora and then Skopeo is installed into them:
|
||||
|
||||
* quay.io/skopeo/stable - This image is built using the latest stable version of Skopeo in a Fedora based container. Built with skopeoimage/stable/Dockerfile.
|
||||
* quay.io/skopeo/upstream - This image is built using the latest code found in this GitHub repository. When someone creates a commit and pushes it, the image is created. Due to that the image changes frequently and is not guaranteed to be stable. Built with skopeoimage/upstream/Dockerfile.
|
||||
* quay.io/skopeo/testing - This image is built using the latest version of Skopeo that is or was in updates testing for Fedora. At times this may be the same as the stable image. This container image will primarily be used by the development teams for verification testing when a new package is created. Built with skopeoimage/testing/Dockerfile.
|
||||
|
||||
## Multiarch images
|
||||
|
||||
Multiarch images are available for Skopeo upstream and stable versions. Supported architectures are `amd64`, `s390x`, `ppc64le`.
|
||||
Available images are `quay.io/skopeo/upstream:master`, `quay.io/skopeo/stable:v1.2.0`, `quay.io/containers/skopeo:v1.2.0`.
|
||||
|
||||
Images can be used the same way as in a single architecture case, no extra setup is required. For samples see next chapter.
|
||||
|
||||
## Sample Usage
|
||||
|
||||
Although not required, it is suggested that [Podman](https://github.com/containers/podman) be used with these container images.
|
||||
|
||||
```
|
||||
# Get Help on Skopeo
|
||||
podman run docker://quay.io/skopeo/stable:latest --help
|
||||
|
||||
# Get help on the Skopeo Copy command
|
||||
podman run docker://quay.io/skopeo/stable:latest copy --help
|
||||
|
||||
# Copy the Skopeo container image from quay.io to
|
||||
# a private registry
|
||||
podman run docker://quay.io/skopeo/stable:latest copy docker://quay.io/skopeo/stable docker://registry.internal.company.com/skopeo
|
||||
|
||||
# Inspect the fedora:latest image
|
||||
podman run docker://quay.io/skopeo/stable:latest inspect --config docker://registry.fedoraproject.org/fedora:latest | jq
|
||||
```
|
||||
33
contrib/skopeoimage/stable/Dockerfile
Normal file
33
contrib/skopeoimage/stable/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
# stable/Dockerfile
|
||||
#
|
||||
# Build a Skopeo container image from the latest
|
||||
# stable version of Skopeo on the Fedoras Updates System.
|
||||
# https://bodhi.fedoraproject.org/updates/?search=skopeo
|
||||
# This image can be used to create a secured container
|
||||
# that runs safely with privileges within the container.
|
||||
#
|
||||
FROM registry.fedoraproject.org/fedora:33
|
||||
|
||||
# Don't include container-selinux and remove
|
||||
# directories used by yum that are just taking
|
||||
# up space. Also reinstall shadow-utils as without
|
||||
# doing so, the setuid/setgid bits on newuidmap
|
||||
# and newgidmap are lost in the Fedora images.
|
||||
RUN useradd skopeo; yum -y update; yum -y reinstall shadow-utils; yum -y install skopeo fuse-overlayfs --exclude container-selinux; yum -y clean all; rm -rf /var/cache/dnf/* /var/log/dnf* /var/log/yum*
|
||||
|
||||
# Adjust storage.conf to enable Fuse storage.
|
||||
RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' /etc/containers/storage.conf
|
||||
|
||||
# Setup the ability to use additional stores
|
||||
# with this container image.
|
||||
RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock
|
||||
|
||||
# Setup skopeo's uid/guid entries
|
||||
RUN echo skopeo:100000:65536 > /etc/subuid
|
||||
RUN echo skopeo:100000:65536 > /etc/subgid
|
||||
|
||||
# Point to the Authorization file
|
||||
ENV REGISTRY_AUTH_FILE=/tmp/auth.json
|
||||
|
||||
# Set the entrypoint
|
||||
ENTRYPOINT ["/usr/bin/skopeo"]
|
||||
34
contrib/skopeoimage/testing/Dockerfile
Normal file
34
contrib/skopeoimage/testing/Dockerfile
Normal file
@@ -0,0 +1,34 @@
|
||||
# testing/Dockerfile
|
||||
#
|
||||
# Build a Skopeo container image from the latest
|
||||
# version of Skopeo that is in updates-testing
|
||||
# on the Fedoras Updates System.
|
||||
# https://bodhi.fedoraproject.org/updates/?search=skopeo
|
||||
# This image can be used to create a secured container
|
||||
# that runs safely with privileges within the container.
|
||||
#
|
||||
FROM registry.fedoraproject.org/fedora:33
|
||||
|
||||
# Don't include container-selinux and remove
|
||||
# directories used by yum that are just taking
|
||||
# up space. Also reinstall shadow-utils as without
|
||||
# doing so, the setuid/setgid bits on newuidmap
|
||||
# and newgidmap are lost in the Fedora images.
|
||||
RUN useradd skopeo; yum -y update; yum -y reinstall shadow-utils; yum -y install skopeo fuse-overlayfs --enablerepo updates-testing --exclude container-selinux; yum -y clean all; rm -rf /var/cache/dnf/* /var/log/dnf* /var/log/yum*
|
||||
|
||||
# Adjust storage.conf to enable Fuse storage.
|
||||
RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' /etc/containers/storage.conf
|
||||
|
||||
# Setup the ability to use additional stores
|
||||
# with this container image.
|
||||
RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock
|
||||
|
||||
# Setup skopeo's uid/guid entries
|
||||
RUN echo skopeo:100000:65536 > /etc/subuid
|
||||
RUN echo skopeo:100000:65536 > /etc/subgid
|
||||
|
||||
# Point to the Authorization file
|
||||
ENV REGISTRY_AUTH_FILE=/tmp/auth.json
|
||||
|
||||
# Set the entrypoint
|
||||
ENTRYPOINT ["/usr/bin/skopeo"]
|
||||
54
contrib/skopeoimage/upstream/Dockerfile
Normal file
54
contrib/skopeoimage/upstream/Dockerfile
Normal file
@@ -0,0 +1,54 @@
|
||||
# upstream/Dockerfile
|
||||
#
|
||||
# Build a Skopeo container image from the latest
|
||||
# upstream version of Skopeo on GitHub.
|
||||
# https://github.com/containers/skopeo
|
||||
# This image can be used to create a secured container
|
||||
# that runs safely with privileges within the container.
|
||||
#
|
||||
FROM registry.fedoraproject.org/fedora:33
|
||||
|
||||
# Don't include container-selinux and remove
|
||||
# directories used by yum that are just taking
|
||||
# up space. Also reinstall shadow-utils as without
|
||||
# doing so, the setuid/setgid bits on newuidmap
|
||||
# and newgidmap are lost in the Fedora images.
|
||||
RUN useradd skopeo; yum -y update; yum -y reinstall shadow-utils; \
|
||||
yum -y install make \
|
||||
golang \
|
||||
git \
|
||||
go-md2man \
|
||||
fuse-overlayfs \
|
||||
fuse3 \
|
||||
containers-common \
|
||||
gpgme-devel \
|
||||
libassuan-devel \
|
||||
btrfs-progs-devel \
|
||||
device-mapper-devel --enablerepo updates-testing --exclude container-selinux; \
|
||||
mkdir /root/skopeo; \
|
||||
git clone https://github.com/containers/skopeo /root/skopeo/src/github.com/containers/skopeo; \
|
||||
export GOPATH=/root/skopeo; \
|
||||
cd /root/skopeo/src/github.com/containers/skopeo; \
|
||||
make bin/skopeo;\
|
||||
make PREFIX=/usr install;\
|
||||
rm -rf /root/skopeo/*; \
|
||||
yum -y remove git golang go-md2man make; \
|
||||
yum -y clean all; yum -y clean all; rm -rf /var/cache/dnf/* /var/log/dnf* /var/log/yum*
|
||||
|
||||
|
||||
# Adjust storage.conf to enable Fuse storage.
|
||||
RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' /etc/containers/storage.conf
|
||||
|
||||
# Setup the ability to use additional stores
|
||||
# with this container image.
|
||||
RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock
|
||||
|
||||
# Setup skopeo's uid/guid entries
|
||||
RUN echo skopeo:100000:65536 > /etc/subuid
|
||||
RUN echo skopeo:100000:65536 > /etc/subgid
|
||||
|
||||
# Point to the Authorization file
|
||||
ENV REGISTRY_AUTH_FILE=/tmp/auth.json
|
||||
|
||||
# Set the entrypoint
|
||||
ENTRYPOINT ["/usr/bin/skopeo"]
|
||||
@@ -15,6 +15,9 @@ Uses the system's trust policy to validate images, rejects images not trusted by
|
||||
|
||||
_destination-image_ use the "image name" format described above
|
||||
|
||||
_source-image_ and _destination-image_ are interpreted completely independently; e.g. the destination name does not
|
||||
automatically inherit any parts of the source name.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--all**
|
||||
@@ -25,7 +28,7 @@ the images in the list, and the list itself.
|
||||
|
||||
**--authfile** _path_
|
||||
|
||||
Path of the authentication file. Default is ${XDG_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`.
|
||||
Path of the authentication file. Default is ${XDG_RUNTIME\_DIR}/containers/auth.json, which is set using `skopeo login`.
|
||||
If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`.
|
||||
|
||||
Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE
|
||||
@@ -39,6 +42,10 @@ Path of the authentication file for the source registry. Uses path given by `--a
|
||||
|
||||
Path of the authentication file for the destination registry. Uses path given by `--authfile`, if not provided.
|
||||
|
||||
**--digestfile** _path_
|
||||
|
||||
After copying the image, write the digest of the resulting image to the file.
|
||||
|
||||
**--format, -f** _manifest-type_ Manifest type (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source)
|
||||
|
||||
**--quiet, -q** suppress output information when copying images
|
||||
@@ -47,29 +54,29 @@ Path of the authentication file for the destination registry. Uses path given by
|
||||
|
||||
**--sign-by=**_key-id_ add a signature using that key ID for an image name corresponding to _destination-image_
|
||||
|
||||
**--encryption-key** _Key_ a reference prefixed with the encryption protocol to use. The supported protocols are JWE, PGP and PKCS7. For instance, jwe:/path/to/key.pem or pgp:admin@example.com or pkcs7:/path/to/x509-file. This feature is still *experimental*.
|
||||
**--encryption-key** _protocol:keyfile_ specifies the encryption protocol, which can be JWE (RFC7516), PGP (RFC4880), and PKCS7 (RFC2315) and the key material required for image encryption. For instance, jwe:/path/to/key.pem or pgp:admin@example.com or pkcs7:/path/to/x509-file.
|
||||
|
||||
**--decryption-key** _Key_ a reference required to perform decryption of container images. This should point to files which represent keys and/or certificates that can be used for decryption. Decryption will be tried with all keys. This feature is still *experimental*.
|
||||
**--decryption-key** _key[:passphrase]_ to be used for decryption of images. Key can point to keys and/or certificates. Decryption will be tried with all keys. If the key is protected by a passphrase, it is required to be passed in the argument and omitted otherwise.
|
||||
|
||||
**--src-creds** _username[:password]_ for accessing the source registry
|
||||
**--src-creds** _username[:password]_ for accessing the source registry.
|
||||
|
||||
**--dest-compress** _bool-value_ Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source)
|
||||
**--dest-compress** _bool-value_ Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source).
|
||||
|
||||
**--dest-oci-accept-uncompressed-layers** _bool-value_ Allow uncompressed image layers when saving to an OCI image using the 'oci' transport. (default is to compress things that aren't compressed)
|
||||
**--dest-oci-accept-uncompressed-layers** _bool-value_ Allow uncompressed image layers when saving to an OCI image using the 'oci' transport. (default is to compress things that aren't compressed).
|
||||
|
||||
**--dest-creds** _username[:password]_ for accessing the destination registry
|
||||
**--dest-creds** _username[:password]_ for accessing the destination registry.
|
||||
|
||||
**--src-cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the source registry or daemon
|
||||
**--src-cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the source registry or daemon.
|
||||
|
||||
**--src-no-creds** _bool-value_ Access the registry anonymously.
|
||||
|
||||
**--src-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container source registry or daemon (defaults to true)
|
||||
**--src-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container source registry or daemon (defaults to true).
|
||||
|
||||
**--dest-cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the destination registry or daemon
|
||||
**--dest-cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the destination registry or daemon.
|
||||
|
||||
**--dest-no-creds** _bool-value_ Access the registry anonymously.
|
||||
|
||||
**--dest-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container destination registry or daemon (defaults to true)
|
||||
**--dest-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container destination registry or daemon (defaults to true).
|
||||
|
||||
**--src-daemon-host** _host_ Copy from docker daemon at _host_. If _host_ starts with `tcp://`, HTTPS is enabled by default. To use plain HTTP, use the form `http://` (default is `unix:///var/run/docker.sock`).
|
||||
|
||||
@@ -81,8 +88,17 @@ Existing signatures, if any, are preserved as well.
|
||||
|
||||
**--dest-compress-level** _format_ Specifies the compression level to use. The value is specific to the compression algorithm used, e.g. for zstd the accepted values are in the range 1-20 (inclusive), while for gzip it is 1-9 (inclusive).
|
||||
|
||||
**--src-registry-token** _Bearer token_ for accessing the source registry.
|
||||
|
||||
**--dest-registry-token** _Bearer token_ for accessing the destination registry.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
To just copy an image from one registry to another:
|
||||
```sh
|
||||
$ skopeo copy docker://quay.io/skopeo/stable:latest docker://registry.example.com/skopeo:latest
|
||||
```
|
||||
|
||||
To copy the layers of the docker.io busybox image to a local directory:
|
||||
```sh
|
||||
$ mkdir -p /var/lib/images/busybox
|
||||
@@ -96,7 +112,7 @@ $ ls /var/lib/images/busybox/*
|
||||
To copy and sign an image:
|
||||
|
||||
```sh
|
||||
# skopeo copy --sign-by dev@example.com container-storage:example/busybox:streaming docker://example/busybox:gold
|
||||
# skopeo copy --sign-by dev@example.com containers-storage:example/busybox:streaming docker://example/busybox:gold
|
||||
```
|
||||
|
||||
To encrypt an image:
|
||||
@@ -132,7 +148,7 @@ skopeo copy --encryption-key jwe:./public.key --encrypt-layer 1 oci:local_nginx
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
skopeo(1), podman-login(1), docker-login(1)
|
||||
skopeo(1), skopeo-login(1), docker-login(1), containers-auth.json(5), containers-policy.json(5), containers-transports(5)
|
||||
|
||||
## AUTHORS
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
% skopeo-delete(1)
|
||||
|
||||
## NAME
|
||||
skopeo\-delete - Mark _image-name_ for deletion.
|
||||
skopeo\-delete - Mark the _image-name_ for later deletion by the registry's garbage collector.
|
||||
|
||||
## SYNOPSIS
|
||||
**skopeo delete** _image-name_
|
||||
@@ -21,19 +21,21 @@ $ docker exec -it registry /usr/bin/registry garbage-collect /etc/docker-distrib
|
||||
|
||||
**--authfile** _path_
|
||||
|
||||
Path of the authentication file. Default is ${XDG_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`.
|
||||
Path of the authentication file. Default is ${XDG_RUNTIME\_DIR}/containers/auth.json, which is set using `skopeo login`.
|
||||
If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`.
|
||||
|
||||
**--creds** _username[:password]_ for accessing the registry
|
||||
**--creds** _username[:password]_ for accessing the registry.
|
||||
|
||||
**--cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the registry
|
||||
**--cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the registry.
|
||||
|
||||
**--tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container registries (defaults to true)
|
||||
**--tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container registries (defaults to true).
|
||||
|
||||
**--no-creds** _bool-value_ Access the registry anonymously.
|
||||
|
||||
Additionally, the registry must allow deletions by setting `REGISTRY_STORAGE_DELETE_ENABLED=true` for the registry daemon.
|
||||
|
||||
**--registry-token** _Bearer token_ for accessing the registry.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Mark image example/pause for deletion from the registry.example.com registry:
|
||||
@@ -44,7 +46,7 @@ See above for additional details on using the command **delete**.
|
||||
|
||||
|
||||
## SEE ALSO
|
||||
skopeo(1), podman-login(1), docker-login(1)
|
||||
skopeo(1), skopeo-login(1), docker-login(1), containers-auth.json(5)
|
||||
|
||||
## AUTHORS
|
||||
|
||||
|
||||
@@ -1,37 +1,61 @@
|
||||
% skopeo-inspect(1)
|
||||
|
||||
## NAME
|
||||
skopeo\-inspect - Return low-level information about _image-name_ in a registry
|
||||
skopeo\-inspect - Return low-level information about _image-name_ in a registry.
|
||||
|
||||
## SYNOPSIS
|
||||
**skopeo inspect** [**--raw**] [**--config**] _image-name_
|
||||
**skopeo inspect** [*options*] _image-name_
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Return low-level information about _image-name_ in a registry
|
||||
|
||||
**--raw** output raw manifest, default is to format in JSON
|
||||
_image-name_ name of image to retrieve information about
|
||||
|
||||
_image-name_ name of image to retrieve information about
|
||||
## OPTIONS
|
||||
|
||||
**--config** output configuration in OCI format, default is to format in JSON
|
||||
**--authfile** _path_
|
||||
|
||||
_image-name_ name of image to retrieve configuration for
|
||||
Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `skopeo login`.
|
||||
If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`.
|
||||
|
||||
**--config** **--raw** output configuration in raw format
|
||||
**--cert-dir** _path_
|
||||
|
||||
_image-name_ name of image to retrieve configuration for
|
||||
Use certificates at _path_ (\*.crt, \*.cert, \*.key) to connect to the registry.
|
||||
|
||||
**--authfile** _path_
|
||||
**--config**
|
||||
|
||||
Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`.
|
||||
If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`.
|
||||
Output configuration in OCI format, default is to format in JSON format.
|
||||
|
||||
**--creds** _username[:password]_ for accessing the registry
|
||||
**--creds** _username[:password]_
|
||||
|
||||
**--cert-dir** _path_ Use certificates at _path_ (\*.crt, \*.cert, \*.key) to connect to the registry
|
||||
Username and password for accessing the registry.
|
||||
|
||||
**--tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container registries (defaults to true)
|
||||
**--format**, **-f**=*format*
|
||||
|
||||
**--no-creds** _bool-value_ Access the registry anonymously.
|
||||
Format the output using the given Go template.
|
||||
The keys of the returned JSON can be used as the values for the --format flag (see examples below).
|
||||
|
||||
**--no-creds**
|
||||
|
||||
Access the registry anonymously.
|
||||
|
||||
**--raw**
|
||||
|
||||
Output raw manifest or config data depending on --config option.
|
||||
The --format option is not supported with --raw option.
|
||||
|
||||
**--registry-token** _Bearer token_
|
||||
|
||||
Registry token for accessing the registry.
|
||||
|
||||
**--retry-times**
|
||||
|
||||
The number of times to retry; retry wait time will be exponentially increased based on the number of failed attempts.
|
||||
|
||||
**--tls-verify**
|
||||
|
||||
Require HTTPS and verify certificates when talking to container registries (defaults to true).
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
@@ -42,14 +66,14 @@ $ skopeo inspect docker://docker.io/fedora
|
||||
"Name": "docker.io/library/fedora",
|
||||
"Digest": "sha256:a97914edb6ba15deb5c5acf87bd6bd5b6b0408c96f48a5cbd450b5b04509bb7d",
|
||||
"RepoTags": [
|
||||
"20",
|
||||
"21",
|
||||
"22",
|
||||
"23",
|
||||
"24",
|
||||
"heisenbug",
|
||||
"latest",
|
||||
"rawhide"
|
||||
"20",
|
||||
"21",
|
||||
"22",
|
||||
"23",
|
||||
"24",
|
||||
"heisenbug",
|
||||
"latest",
|
||||
"rawhide"
|
||||
],
|
||||
"Created": "2016-06-20T19:33:43.220526898Z",
|
||||
"DockerVersion": "1.10.3",
|
||||
@@ -57,15 +81,24 @@ $ skopeo inspect docker://docker.io/fedora
|
||||
"Architecture": "amd64",
|
||||
"Os": "linux",
|
||||
"Layers": [
|
||||
"sha256:7c91a140e7a1025c3bc3aace4c80c0d9933ac4ee24b8630a6b0b5d8b9ce6b9d4"
|
||||
"sha256:7c91a140e7a1025c3bc3aace4c80c0d9933ac4ee24b8630a6b0b5d8b9ce6b9d4"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
$ /bin/skopeo inspect --config docker://registry.fedoraproject.org/fedora --format "{{ .Architecture }}"
|
||||
amd64
|
||||
```
|
||||
|
||||
```
|
||||
$ /bin/skopeo inspect --format '{{ .Env }}' docker://registry.access.redhat.com/ubi8
|
||||
[PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin container=oci]
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
skopeo(1), podman-login(1), docker-login(1)
|
||||
skopeo(1), skopeo-login(1), docker-login(1), containers-auth.json(5)
|
||||
|
||||
## AUTHORS
|
||||
|
||||
Antonio Murdaca <runcom@redhat.com>, Miloslav Trmac <mitr@redhat.com>, Jhon Honce <jhonce@redhat.com>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
% skopeo-list-tags(1)
|
||||
|
||||
## NAME
|
||||
skopeo\-list\-tags - Return a list of tags the transport-specific image repository
|
||||
skopeo\-list\-tags - Return a list of tags for the transport-specific image repository.
|
||||
|
||||
## SYNOPSIS
|
||||
**skopeo list-tags** _repository-name_
|
||||
@@ -12,17 +12,19 @@ Return a list of tags from _repository-name_ in a registry.
|
||||
|
||||
**--authfile** _path_
|
||||
|
||||
Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`.
|
||||
Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `skopeo login`.
|
||||
If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`.
|
||||
|
||||
**--creds** _username[:password]_ for accessing the registry
|
||||
**--creds** _username[:password]_ for accessing the registry.
|
||||
|
||||
**--cert-dir** _path_ Use certificates at _path_ (\*.crt, \*.cert, \*.key) to connect to the registry
|
||||
**--cert-dir** _path_ Use certificates at _path_ (\*.crt, \*.cert, \*.key) to connect to the registry.
|
||||
|
||||
**--tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container registries (defaults to true)
|
||||
**--tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container registries (defaults to true).
|
||||
|
||||
**--no-creds** _bool-value_ Access the registry anonymously.
|
||||
|
||||
**--registry-token** _Bearer token_ for accessing the registry.
|
||||
|
||||
## REPOSITORY NAMES
|
||||
|
||||
Repository names are transport-specific references as each transport may have its own concept of a "repository" and "tags". Currently, only the Docker transport is supported.
|
||||
@@ -30,7 +32,7 @@ Repository names are transport-specific references as each transport may have it
|
||||
This commands refers to repositories using a _transport_`:`_details_ format. The following formats are supported:
|
||||
|
||||
**docker://**_docker-repository-reference_
|
||||
A repository in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in either `$XDG_RUNTIME_DIR/containers/auth.json`, which is set using `(podman login)`. If the authorization state is not found there, `$HOME/.docker/config.json` is checked, which is set using `(docker login)`.
|
||||
A repository in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in either `$XDG_RUNTIME_DIR/containers/auth.json`, which is set using `(skopeo login)`. If the authorization state is not found there, `$HOME/.docker/config.json` is checked, which is set using `(docker login)`.
|
||||
A _docker-repository-reference_ is of the form: **registryhost:port/repositoryname** which is similar to an _image-reference_ but with no tag or digest allowed as the last component (e.g no `:latest` or `@sha256:xyz`)
|
||||
|
||||
Examples of valid docker-repository-references:
|
||||
@@ -94,7 +96,7 @@ $ skopeo list-tags docker://localhost:5000/fedora
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
skopeo(1), podman-login(1), docker-login(1)
|
||||
skopeo(1), skopeo-login(1), docker-login(1), containers-auth.json(5)
|
||||
|
||||
## AUTHORS
|
||||
|
||||
|
||||
101
docs/skopeo-login.1.md
Normal file
101
docs/skopeo-login.1.md
Normal file
@@ -0,0 +1,101 @@
|
||||
% skopeo-login(1)
|
||||
|
||||
## NAME
|
||||
skopeo\-login - Login to a container registry.
|
||||
|
||||
## SYNOPSIS
|
||||
**skopeo login** [*options*] *registry*
|
||||
|
||||
## DESCRIPTION
|
||||
**skopeo login** logs into a specified registry server with the correct username
|
||||
and password. **skopeo login** reads in the username and password from STDIN.
|
||||
The username and password can also be set using the **username** and **password** flags.
|
||||
The path of the authentication file can be specified by the user by setting the **authfile**
|
||||
flag. The default path used is **${XDG\_RUNTIME\_DIR}/containers/auth.json**.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--password**, **-p**=*password*
|
||||
|
||||
Password for registry
|
||||
|
||||
**--password-stdin**
|
||||
|
||||
Take the password from stdin
|
||||
|
||||
**--username**, **-u**=*username*
|
||||
|
||||
Username for registry
|
||||
|
||||
**--authfile**=*path*
|
||||
|
||||
Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json
|
||||
|
||||
Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE
|
||||
environment variable. `export REGISTRY_AUTH_FILE=path`
|
||||
|
||||
**--get-login**
|
||||
|
||||
Return the logged-in user for the registry. Return error if no login is found.
|
||||
|
||||
**--cert-dir**=*path*
|
||||
|
||||
Use certificates at *path* (\*.crt, \*.cert, \*.key) to connect to the registry.
|
||||
Default certificates directory is _/etc/containers/certs.d_.
|
||||
|
||||
**--tls-verify**=*true|false*
|
||||
|
||||
Require HTTPS and verify certificates when contacting registries (default: true). If explicitly set to true,
|
||||
then TLS verification will be used. If set to false, then TLS verification will not be used. If not specified,
|
||||
TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf.
|
||||
|
||||
**--help**, **-h**
|
||||
|
||||
Print usage statement
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
$ skopeo login docker.io
|
||||
Username: testuser
|
||||
Password:
|
||||
Login Succeeded!
|
||||
```
|
||||
|
||||
```
|
||||
$ skopeo login -u testuser -p testpassword localhost:5000
|
||||
Login Succeeded!
|
||||
```
|
||||
|
||||
```
|
||||
$ skopeo login --authfile authdir/myauths.json docker.io
|
||||
Username: testuser
|
||||
Password:
|
||||
Login Succeeded!
|
||||
```
|
||||
|
||||
```
|
||||
$ skopeo login --tls-verify=false -u test -p test localhost:5000
|
||||
Login Succeeded!
|
||||
```
|
||||
|
||||
```
|
||||
$ skopeo login --cert-dir /etc/containers/certs.d/ -u foo -p bar localhost:5000
|
||||
Login Succeeded!
|
||||
```
|
||||
|
||||
```
|
||||
$ skopeo login -u testuser --password-stdin < testpassword.txt docker.io
|
||||
Login Succeeded!
|
||||
```
|
||||
|
||||
```
|
||||
$ echo $testpassword | skopeo login -u testuser --password-stdin docker.io
|
||||
Login Succeeded!
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
skopeo(1), skopeo-logout(1), containers-auth.json(5), containers-registries.conf(5), containers-certs.d.5.md
|
||||
|
||||
## HISTORY
|
||||
May 2020, Originally compiled by Qi Wang <qiwan@redhat.com>
|
||||
53
docs/skopeo-logout.1.md
Normal file
53
docs/skopeo-logout.1.md
Normal file
@@ -0,0 +1,53 @@
|
||||
% skopeo-logout(1)
|
||||
|
||||
## NAME
|
||||
skopeo\-logout - Logout of a container registry.
|
||||
|
||||
## SYNOPSIS
|
||||
**skopeo logout** [*options*] *registry*
|
||||
|
||||
## DESCRIPTION
|
||||
**skopeo logout** logs out of a specified registry server by deleting the cached credentials
|
||||
stored in the **auth.json** file. The path of the authentication file can be overridden by the user by setting the **authfile** flag.
|
||||
The default path used is **${XDG\_RUNTIME\_DIR}/containers/auth.json**.
|
||||
All the cached credentials can be removed by setting the **all** flag.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--authfile**=*path*
|
||||
|
||||
Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json
|
||||
|
||||
Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE
|
||||
environment variable. `export REGISTRY_AUTH_FILE=path`
|
||||
|
||||
**--all**, **-a**
|
||||
|
||||
Remove the cached credentials for all registries in the auth file
|
||||
|
||||
**--help**, **-h**
|
||||
|
||||
Print usage statement
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
$ skopeo logout docker.io
|
||||
Remove login credentials for docker.io
|
||||
```
|
||||
|
||||
```
|
||||
$ skopeo logout --authfile authdir/myauths.json docker.io
|
||||
Remove login credentials for docker.io
|
||||
```
|
||||
|
||||
```
|
||||
$ skopeo logout --all
|
||||
Remove login credentials for all registries
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
skopeo(1), skopeo-login(1), containers-auth.json(5)
|
||||
|
||||
## HISTORY
|
||||
May 2020, Originally compiled by Qi Wang <qiwan@redhat.com>
|
||||
@@ -1,7 +1,7 @@
|
||||
% skopeo-manifest-digest(1)
|
||||
|
||||
## NAME
|
||||
skopeo\-manifest\-digest -Compute a manifest digest of manifest-file and write it to standard output.
|
||||
skopeo\-manifest\-digest - Compute a manifest digest for a manifest-file and write it to standard output.
|
||||
|
||||
## SYNOPSIS
|
||||
**skopeo manifest-digest** _manifest-file_
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
% skopeo-standalone-sign(1)
|
||||
|
||||
## NAME
|
||||
skopeo\-standalone-sign - Simple Sign an image
|
||||
skopeo\-standalone-sign - Debugging tool - Publish and sign an image in one step.
|
||||
|
||||
## SYNOPSIS
|
||||
**skopeo standalone-sign** _manifest docker-reference key-fingerprint_ **--output**|**-o** _signature_
|
||||
@@ -26,7 +26,7 @@ $
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
skopeo(1), skopeo-copy(1)
|
||||
skopeo(1), skopeo-copy(1), containers-signature(5)
|
||||
|
||||
## AUTHORS
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
% skopeo-standalone-verify(1)
|
||||
|
||||
## NAME
|
||||
skopeo\-standalone\-verify - Verify an image signature
|
||||
skopeo\-standalone\-verify - Verify an image signature.
|
||||
|
||||
## SYNOPSIS
|
||||
**skopeo standalone-verify** _manifest docker-reference key-fingerprint signature_
|
||||
@@ -28,7 +28,7 @@ Signature verified, digest sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
skopeo(1)
|
||||
skopeo(1), containers-signature(5)
|
||||
|
||||
## AUTHORS
|
||||
|
||||
|
||||
@@ -32,9 +32,14 @@ When the `--scoped` option is specified, images are prefixed with the source ima
|
||||
name can be stored at _destination_.
|
||||
|
||||
## OPTIONS
|
||||
**--all**
|
||||
If one of the images in __src__ refers to a list of images, instead of copying just the image which matches the current OS and
|
||||
architecture (subject to the use of the global --override-os, --override-arch and --override-variant options), attempt to copy all of
|
||||
the images in the list, and the list itself.
|
||||
|
||||
**--authfile** _path_
|
||||
|
||||
Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`.
|
||||
Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `skopeo login`.
|
||||
If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`.
|
||||
|
||||
**--src-authfile** _path_
|
||||
@@ -49,6 +54,8 @@ Path of the authentication file for the destination registry. Uses path given by
|
||||
|
||||
**--dest** _transport_ Destination transport.
|
||||
|
||||
**--format, -f** _manifest-type_ Manifest Type (oci, v2s1, or v2s2) to use when syncing image(s) to a destination (default is manifest type of source).
|
||||
|
||||
**--scoped** Prefix images with the source image path, so that multiple images with the same name can be stored at _destination_.
|
||||
|
||||
**--remove-signatures** Do not copy signatures, if any, from _source-image_. This is necessary when copying a signed image to a destination which does not support signatures.
|
||||
@@ -71,6 +78,10 @@ Path of the authentication file for the destination registry. Uses path given by
|
||||
|
||||
**--dest-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to a container destination registry or daemon (defaults to true).
|
||||
|
||||
**--src-registry-token** _Bearer token_ for accessing the source registry.
|
||||
|
||||
**--dest-registry-token** _Bearer token_ for accessing the destination registry.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
### Synchronizing to a local directory
|
||||
@@ -86,6 +97,21 @@ Images are located at:
|
||||
/media/usb/busybox:latest
|
||||
```
|
||||
|
||||
### Synchronizing to a container registry from local
|
||||
Images are located at:
|
||||
```
|
||||
/media/usb/busybox:1-glibc
|
||||
```
|
||||
Sync run
|
||||
```
|
||||
$ skopeo sync --src dir --dest docker /media/usb/busybox:1-glibc my-registry.local.lan/test/
|
||||
```
|
||||
Destination registry content:
|
||||
```
|
||||
REPO TAGS
|
||||
my-registry.local.lan/test/busybox 1-glibc
|
||||
```
|
||||
|
||||
### Synchronizing to a local directory, scoped
|
||||
```
|
||||
$ skopeo sync --src docker --dest dir --scoped registry.example.com/busybox /media/usb
|
||||
@@ -128,6 +154,9 @@ registry.example.com:
|
||||
redis:
|
||||
- "1.0"
|
||||
- "2.0"
|
||||
- "sha256:0000000000000000000000000000000011111111111111111111111111111111"
|
||||
images-by-tag-regex:
|
||||
nginx: ^1\.13\.[12]-alpine-perl$
|
||||
credentials:
|
||||
username: john
|
||||
password: this is a secret
|
||||
@@ -139,22 +168,25 @@ quay.io:
|
||||
coreos/etcd:
|
||||
- latest
|
||||
```
|
||||
|
||||
If the yaml filename is `sync.yml`, sync run:
|
||||
```
|
||||
skopeo sync --src yaml --dest docker sync.yml my-registry.local.lan/repo/
|
||||
```
|
||||
This will copy the following images:
|
||||
- Repository `registry.example.com/busybox`: all images, as no tags are specified.
|
||||
- Repository `registry.example.com/redis`: images tagged "1.0" and "2.0".
|
||||
- Repository `registry.example.com/redis`: images tagged "1.0" and "2.0" along with image with digest "sha256:0000000000000000000000000000000011111111111111111111111111111111".
|
||||
- Repository `registry.example.com/nginx`: images tagged "1.13.1-alpine-perl" and "1.13.2-alpine-perl".
|
||||
- Repository `quay.io/coreos/etcd`: images tagged "latest".
|
||||
|
||||
For the registry `registry.example.com`, the "john"/"this is a secret" credentials are used, with server TLS certificates located at `/home/john/certs`.
|
||||
|
||||
TLS verification is normally enabled, and it can be disabled setting `tls-verify` to `true`.
|
||||
In the above example, TLS verification is enabled for `reigstry.example.com`, while is
|
||||
TLS verification is normally enabled, and it can be disabled setting `tls-verify` to `false`.
|
||||
In the above example, TLS verification is enabled for `registry.example.com`, while is
|
||||
disabled for `quay.io`.
|
||||
|
||||
## SEE ALSO
|
||||
skopeo(1), podman-login(1), docker-login(1)
|
||||
skopeo(1), skopeo-login(1), docker-login(1), containers-auth.json(5), containers-policy.json(5), containers-transports(5)
|
||||
|
||||
## AUTHORS
|
||||
|
||||
Flavio Castelli <fcastelli@suse.com>, Marco Vedovati <mvedovati@suse.com>
|
||||
|
||||
|
||||
@@ -27,13 +27,13 @@ its functionality. It also does not require root, unless you are copying images
|
||||
Most commands refer to container images, using a _transport_`:`_details_ format. The following formats are supported:
|
||||
|
||||
**containers-storage:**_docker-reference_
|
||||
An image located in a local containers/storage image store. Location and image store specified in /etc/containers/storage.conf
|
||||
An image located in a local containers/storage image store. Both the location and image store are specified in /etc/containers/storage.conf. (Backend for Podman, CRI-O, Buildah and friends)
|
||||
|
||||
**dir:**_path_
|
||||
An existing local directory _path_ storing the manifest, layer tarballs and signatures as individual files. This is a non-standardized format, primarily useful for debugging or noninvasive container inspection.
|
||||
|
||||
**docker://**_docker-reference_
|
||||
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in either `$XDG_RUNTIME_DIR/containers/auth.json`, which is set using `(podman login)`. If the authorization state is not found there, `$HOME/.docker/config.json` is checked, which is set using `(docker login)`.
|
||||
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in either `$XDG_RUNTIME_DIR/containers/auth.json`, which is set using `(skopeo login)`. If the authorization state is not found there, `$HOME/.docker/config.json` is checked, which is set using `(docker login)`.
|
||||
|
||||
**docker-archive:**_path_[**:**_docker-reference_]
|
||||
An image is stored in the `docker save` formatted file. _docker-reference_ is only used when creating such a file, and it must not contain a digest.
|
||||
@@ -44,6 +44,11 @@ Most commands refer to container images, using a _transport_`:`_details_ format.
|
||||
**oci:**_path_**:**_tag_
|
||||
An image _tag_ in a directory compliant with "Open Container Image Layout Specification" at _path_.
|
||||
|
||||
**oci-archive:**_path_**:**_tag_
|
||||
An image _tag_ in a tar archive compliant with "Open Container Image Layout Specification" at _path_.
|
||||
|
||||
See [containers-transports(5)](https://github.com/containers/image/blob/master/docs/containers-transports.5.md) for details.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--command-timeout** _duration_ Timeout for the command execution.
|
||||
@@ -76,6 +81,8 @@ Most commands refer to container images, using a _transport_`:`_details_ format.
|
||||
| [skopeo-delete(1)](skopeo-delete.1.md) | Mark image-name for deletion. |
|
||||
| [skopeo-inspect(1)](skopeo-inspect.1.md) | Return low-level information about image-name in a registry. |
|
||||
| [skopeo-list-tags(1)](skopeo-list-tags.1.md) | List the tags for the given transport/repository. |
|
||||
| [skopeo-login(1)](skopeo-login.1.md) | Login to a container registry. |
|
||||
| [skopeo-logout(1)](skopeo-logout.1.md) | Logout of a container registry. |
|
||||
| [skopeo-manifest-digest(1)](skopeo-manifest-digest.1.md) | Compute a manifest digest of manifest-file and write it to standard output.|
|
||||
| [skopeo-standalone-sign(1)](skopeo-standalone-sign.1.md) | Sign an image. |
|
||||
| [skopeo-standalone-verify(1)](skopeo-standalone-verify.1.md)| Verify an image. |
|
||||
@@ -84,14 +91,14 @@ Most commands refer to container images, using a _transport_`:`_details_ format.
|
||||
## FILES
|
||||
**/etc/containers/policy.json**
|
||||
Default trust policy file, if **--policy** is not specified.
|
||||
The policy format is documented in https://github.com/containers/image/blob/master/docs/containers-policy.json.5.md .
|
||||
The policy format is documented in [containers-policy.json(5)](https://github.com/containers/image/blob/master/docs/containers-policy.json.5.md) .
|
||||
|
||||
**/etc/containers/registries.d**
|
||||
Default directory containing registry configuration, if **--registries.d** is not specified.
|
||||
The contents of this directory are documented in https://github.com/containers/image/blob/master/docs/containers-policy.json.5.md .
|
||||
The contents of this directory are documented in [containers-policy.json(5)](https://github.com/containers/image/blob/master/docs/containers-policy.json.5.md).
|
||||
|
||||
## SEE ALSO
|
||||
podman-login(1), docker-login(1)
|
||||
skopeo-login(1), docker-login(1), containers-auth.json(5), containers-storage.conf(5), containers-policy.json(5), containers-transports(5)
|
||||
|
||||
## AUTHORS
|
||||
|
||||
|
||||
27
go.mod
27
go.mod
@@ -3,24 +3,23 @@ module github.com/containers/skopeo
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/containerd/containerd v1.3.0 // indirect
|
||||
github.com/containers/image/v5 v5.4.3
|
||||
github.com/containers/ocicrypt v1.0.2
|
||||
github.com/containers/storage v1.18.2
|
||||
github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/containers/common v0.38.12
|
||||
github.com/containers/image/v5 v5.12.0
|
||||
github.com/containers/ocicrypt v1.1.1
|
||||
github.com/containers/storage v1.31.3
|
||||
github.com/docker/docker v20.10.6+incompatible
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127
|
||||
github.com/google/go-cmp v0.3.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6
|
||||
github.com/opencontainers/image-tools v0.0.0-20170926011501-6d941547fa1d
|
||||
github.com/opencontainers/runtime-spec v1.0.0 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/russross/blackfriday v2.0.0+incompatible // indirect
|
||||
github.com/sirupsen/logrus v1.5.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2
|
||||
github.com/urfave/cli v1.22.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
|
||||
go4.org v0.0.0-20190218023631-ce4c26f7be8e // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
61
hack/get_ci_vm.sh
Executable file
61
hack/get_ci_vm.sh
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# For help and usage information, simply execute the script w/o any arguments.
|
||||
#
|
||||
# This script is intended to be run by Red Hat skopeo developers who need
|
||||
# to debug problems specifically related to Cirrus-CI automated testing.
|
||||
# It requires that you have been granted prior access to create VMs in
|
||||
# google-cloud. For non-Red Hat contributors, VMs are available as-needed,
|
||||
# with supervision upon request.
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_FILEPATH=$(realpath "${BASH_SOURCE[0]}")
|
||||
SCRIPT_DIRPATH=$(dirname "$SCRIPT_FILEPATH")
|
||||
REPO_DIRPATH=$(realpath "$SCRIPT_DIRPATH/../")
|
||||
|
||||
# Help detect if we were called by get_ci_vm container
|
||||
GET_CI_VM="${GET_CI_VM:-0}"
|
||||
in_get_ci_vm() {
|
||||
if ((GET_CI_VM==0)); then
|
||||
echo "Error: $1 is not intended for use in this context"
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
# get_ci_vm APIv1 container entrypoint calls into this script
|
||||
# to obtain required repo. specific configuration options.
|
||||
if [[ "$1" == "--config" ]]; then
|
||||
in_get_ci_vm "$1"
|
||||
cat <<EOF
|
||||
DESTDIR="/var/tmp/go/src/github.com/containers/skopeo"
|
||||
UPSTREAM_REPO="https://github.com/containers/skopeo.git"
|
||||
GCLOUD_PROJECT="skopeo"
|
||||
GCLOUD_IMGPROJECT="libpod-218412"
|
||||
GCLOUD_CFG="skopeo"
|
||||
GCLOUD_ZONE="${GCLOUD_ZONE:-us-central1-f}"
|
||||
GCLOUD_CPUS="2"
|
||||
GCLOUD_MEMORY="4Gb"
|
||||
GCLOUD_DISK="200"
|
||||
EOF
|
||||
elif [[ "$1" == "--setup" ]]; then
|
||||
in_get_ci_vm "$1"
|
||||
# get_ci_vm container entrypoint calls us with this option on the
|
||||
# Cirrus-CI environment instance, to perform repo.-specific setup.
|
||||
echo "+ Executing setup" > /dev/stderr
|
||||
${GOSRC}/${SCRIPT_BASE}/runner.sh setup
|
||||
else
|
||||
# Create and access VM for specified Cirrus-CI task
|
||||
mkdir -p $HOME/.config/gcloud/ssh
|
||||
podman run -it --rm \
|
||||
--tz=local \
|
||||
-e NAME="$USER" \
|
||||
-e SRCDIR=/src \
|
||||
-e GCLOUD_ZONE="$GCLOUD_ZONE" \
|
||||
-e DEBUG="${DEBUG:-0}" \
|
||||
-v $REPO_DIRPATH:/src:O \
|
||||
-v $HOME/.config/gcloud:/root/.config/gcloud:z \
|
||||
-v $HOME/.config/gcloud/ssh:/root/.ssh:z \
|
||||
quay.io/libpod/get_ci_vm:latest "$@"
|
||||
fi
|
||||
11
hack/make.sh
11
hack/make.sh
@@ -25,12 +25,8 @@ export MAKEDIR="$SCRIPTDIR/make"
|
||||
|
||||
# We're a nice, sexy, little shell script, and people might try to run us;
|
||||
# but really, they shouldn't. We want to be in a container!
|
||||
inContainer="AssumeSoInitially"
|
||||
if [ "$PWD" != "/go/src/$SKOPEO_PKG" ]; then
|
||||
unset inContainer
|
||||
fi
|
||||
|
||||
if [ -z "$inContainer" ]; then
|
||||
# The magic value is defined inside our Dockerfile.
|
||||
if [[ "$container_magic" != "85531765-346b-4316-bdb8-358e4cca9e5d" ]]; then
|
||||
{
|
||||
echo "# WARNING! I don't seem to be running in a Docker container."
|
||||
echo "# The result of this command might be an incorrect build, and will not be"
|
||||
@@ -39,6 +35,9 @@ if [ -z "$inContainer" ]; then
|
||||
echo "# Try this instead: make all"
|
||||
echo "#"
|
||||
} >&2
|
||||
else
|
||||
echo "# I appear to be running inside my designated container image, good!"
|
||||
export SKOPEO_CONTAINER_TESTS=1
|
||||
fi
|
||||
|
||||
echo
|
||||
|
||||
@@ -5,7 +5,7 @@ if [ -z "$VALIDATE_UPSTREAM" ]; then
|
||||
# are running more than one validate bundlescript
|
||||
|
||||
VALIDATE_REPO='https://github.com/containers/skopeo.git'
|
||||
VALIDATE_BRANCH='master'
|
||||
VALIDATE_BRANCH='main'
|
||||
|
||||
if [ "$TRAVIS" = 'true' -a "$TRAVIS_PULL_REQUEST" != 'false' ]; then
|
||||
VALIDATE_REPO="https://github.com/${TRAVIS_REPO_SLUG}.git"
|
||||
|
||||
@@ -8,7 +8,7 @@ bundle_test_integration() {
|
||||
|
||||
# subshell so that we can export PATH without breaking other things
|
||||
(
|
||||
make binary-local ${BUILDTAGS:+BUILDTAGS="$BUILDTAGS"}
|
||||
make install
|
||||
make bin/skopeo ${BUILDTAGS:+BUILDTAGS="$BUILDTAGS"}
|
||||
make PREFIX=/usr install
|
||||
bundle_test_integration
|
||||
) 2>&1
|
||||
|
||||
@@ -11,8 +11,8 @@ sed -i \
|
||||
/etc/containers/storage.conf
|
||||
|
||||
# Build skopeo, install into /usr/bin
|
||||
make binary-local ${BUILDTAGS:+BUILDTAGS="$BUILDTAGS"}
|
||||
make install
|
||||
make bin/skopeo ${BUILDTAGS:+BUILDTAGS="$BUILDTAGS"}
|
||||
make PREFIX=/usr install
|
||||
|
||||
# Run tests
|
||||
SKOPEO_BINARY=/usr/bin/skopeo bats --tap systemtest
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
export GOPATH=$(pwd)/_gopath
|
||||
export PATH=$GOPATH/bin:$PATH
|
||||
|
||||
_containers="${GOPATH}/src/github.com/containers"
|
||||
mkdir -vp ${_containers}
|
||||
ln -vsf $(pwd) ${_containers}/skopeo
|
||||
|
||||
go version
|
||||
GO111MODULE=off go get -u github.com/cpuguy83/go-md2man golang.org/x/lint/golint
|
||||
|
||||
cd ${_containers}/skopeo
|
||||
make validate-local test-unit-local binary-local
|
||||
sudo make install
|
||||
skopeo -v
|
||||
179
install.md
179
install.md
@@ -1,70 +1,123 @@
|
||||
# Installing from packages
|
||||
|
||||
`skopeo` may already be packaged in your distribution, for example on
|
||||
RHEL/CentOS ≥ 8 or Fedora you can install it using:
|
||||
## Distribution Packages
|
||||
`skopeo` may already be packaged in your distribution.
|
||||
|
||||
### Fedora
|
||||
|
||||
```sh
|
||||
$ sudo dnf install skopeo
|
||||
sudo dnf -y install skopeo
|
||||
```
|
||||
|
||||
on RHEL/CentOS ≤ 7.x:
|
||||
### RHEL/CentOS ≥ 8 and CentOS Stream
|
||||
|
||||
```sh
|
||||
$ sudo yum install skopeo
|
||||
sudo dnf -y install skopeo
|
||||
```
|
||||
|
||||
for openSUSE:
|
||||
Newer Skopeo releases may be available on the repositories provided by the
|
||||
Kubic project. Beware, these may not be suitable for production environments.
|
||||
|
||||
on CentOS 8:
|
||||
|
||||
```sh
|
||||
$ sudo zypper install skopeo
|
||||
sudo dnf -y module disable container-tools
|
||||
sudo dnf -y install 'dnf-command(copr)'
|
||||
sudo dnf -y copr enable rhcontainerbot/container-selinux
|
||||
sudo curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/CentOS_8/devel:kubic:libcontainers:stable.repo
|
||||
sudo dnf -y install skopeo
|
||||
```
|
||||
|
||||
on alpine:
|
||||
on CentOS 8 Stream:
|
||||
|
||||
```sh
|
||||
$ sudo apk add skopeo
|
||||
sudo dnf -y module disable container-tools
|
||||
sudo dnf -y install 'dnf-command(copr)'
|
||||
sudo dnf -y copr enable rhcontainerbot/container-selinux
|
||||
sudo curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/CentOS_8_Stream/devel:kubic:libcontainers:stable.repo
|
||||
sudo dnf -y install skopeo
|
||||
```
|
||||
|
||||
Debian (10 and newer including Raspbian) and Ubuntu (18.04 and newer): Packages
|
||||
are available via the [Kubic][0] project repositories:
|
||||
### RHEL/CentOS ≤ 7.x
|
||||
|
||||
[0]: https://build.opensuse.org/project/show/devel:kubic:libcontainers:stable
|
||||
```sh
|
||||
sudo yum -y install skopeo
|
||||
```
|
||||
|
||||
### openSUSE
|
||||
|
||||
```sh
|
||||
sudo zypper install skopeo
|
||||
```
|
||||
|
||||
### Alpine
|
||||
|
||||
```sh
|
||||
sudo apk add skopeo
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
```sh
|
||||
brew install skopeo
|
||||
```
|
||||
|
||||
### Nix / NixOS
|
||||
```sh
|
||||
$ nix-env -i skopeo
|
||||
```
|
||||
|
||||
### Debian
|
||||
|
||||
The skopeo package is available in
|
||||
the [Bullseye (testing) branch](https://packages.debian.org/bullseye/skopeo), which
|
||||
will be the next stable release (Debian 11) as well as Debian Unstable/Sid.
|
||||
|
||||
```bash
|
||||
# Debian Unstable/Sid:
|
||||
$ echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_Unstable/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
|
||||
$ wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/Debian_Unstable/Release.key -O- | sudo apt-key add -
|
||||
# Debian Testing/Bullseye or Unstable/Sid
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install skopeo
|
||||
```
|
||||
|
||||
```bash
|
||||
# Debian Testing:
|
||||
$ echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_Testing/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
|
||||
$ wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/Debian_Testing/Release.key -O- | sudo apt-key add -
|
||||
```
|
||||
### Raspberry Pi OS arm64 (beta)
|
||||
|
||||
Raspberry Pi OS uses the standard Debian's repositories,
|
||||
so it is fully compatible with Debian's arm64 repository.
|
||||
You can simply follow the [steps for Debian](#debian) to install Skopeo.
|
||||
|
||||
|
||||
### Ubuntu
|
||||
|
||||
The skopeo package is available in the official repositories for Ubuntu 20.10
|
||||
and newer.
|
||||
|
||||
```bash
|
||||
# Debian 10:
|
||||
$ echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_10/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
|
||||
$ wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/Debian_10/Release.key -O- | sudo apt-key add -
|
||||
# Ubuntu 20.10 and newer
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install skopeo
|
||||
```
|
||||
|
||||
```bash
|
||||
# Raspbian 10:
|
||||
$ echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Raspbian_10/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
|
||||
$ wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/Raspbian_10/Release.key -O- | sudo apt-key add -
|
||||
```
|
||||
If you would prefer newer (though not as well-tested) packages,
|
||||
the [Kubic project](https://build.opensuse.org/package/show/devel:kubic:libcontainers:stable/skopeo)
|
||||
provides packages for active Ubuntu releases 20.04 and newer (it should also work with direct derivatives like Pop!\_OS).
|
||||
Checkout the [Kubic project page](https://build.opensuse.org/package/show/devel:kubic:libcontainers:stable/skopeo)
|
||||
for a list of supported Ubuntu version and
|
||||
architecture combinations. **NOTE:** The command `sudo apt-get -y upgrade`
|
||||
maybe required in some cases if Skopeo cannot be installed without it.
|
||||
The build sources for the Kubic packages can be found [here](https://gitlab.com/rhcontainerbot/skopeo/-/tree/debian/debian).
|
||||
|
||||
CAUTION: On Ubuntu 20.10 and newer, we highly recommend you use Buildah, Podman and Skopeo ONLY from EITHER the Kubic repo
|
||||
OR the official Ubuntu repos. Mixing and matching may lead to unpredictable situations including installation conflicts.
|
||||
|
||||
```bash
|
||||
# Ubuntu (18.04, 19.04 and 19.10):
|
||||
$ . /etc/os-release
|
||||
$ sudo sh -c "echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/x${NAME}_${VERSION_ID}/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list"
|
||||
$ wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/x${NAME}_${VERSION_ID}/Release.key -O- | sudo apt-key add -
|
||||
. /etc/os-release
|
||||
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /" | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
|
||||
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/Release.key | sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get -y upgrade
|
||||
sudo apt-get -y install skopeo
|
||||
```
|
||||
|
||||
```bash
|
||||
$ sudo apt-get update -qq
|
||||
$ sudo apt-get install skopeo
|
||||
```
|
||||
|
||||
Otherwise, read on for building and installing it from source:
|
||||
|
||||
@@ -73,6 +126,8 @@ To build the `skopeo` binary you need at least Go 1.12.
|
||||
There are two ways to build skopeo: in a container, or locally without a
|
||||
container. Choose the one which better matches your needs and environment.
|
||||
|
||||
## Building from Source
|
||||
|
||||
### Building without a container
|
||||
|
||||
Building without a container requires a bit more manual work and setup in your
|
||||
@@ -86,49 +141,32 @@ Install the necessary dependencies:
|
||||
|
||||
```bash
|
||||
# Fedora:
|
||||
$ sudo dnf install gpgme-devel libassuan-devel btrfs-progs-devel device-mapper-devel
|
||||
sudo dnf install gpgme-devel libassuan-devel btrfs-progs-devel device-mapper-devel
|
||||
```
|
||||
|
||||
```bash
|
||||
# Ubuntu (`libbtrfs-dev` requires Ubuntu 18.10 and above):
|
||||
$ sudo apt install libgpgme-dev libassuan-dev libbtrfs-dev libdevmapper-dev
|
||||
sudo apt install libgpgme-dev libassuan-dev libbtrfs-dev libdevmapper-dev
|
||||
```
|
||||
|
||||
```bash
|
||||
# macOS:
|
||||
$ brew install gpgme
|
||||
brew install gpgme
|
||||
```
|
||||
|
||||
```bash
|
||||
# openSUSE:
|
||||
$ sudo zypper install libgpgme-devel device-mapper-devel libbtrfs-devel glib2-devel
|
||||
sudo zypper install libgpgme-devel device-mapper-devel libbtrfs-devel glib2-devel
|
||||
```
|
||||
|
||||
Make sure to clone this repository in your `GOPATH` - otherwise compilation fails.
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/containers/skopeo $GOPATH/src/github.com/containers/skopeo
|
||||
$ cd $GOPATH/src/github.com/containers/skopeo && make binary-local
|
||||
git clone https://github.com/containers/skopeo $GOPATH/src/github.com/containers/skopeo
|
||||
cd $GOPATH/src/github.com/containers/skopeo && make bin/skopeo
|
||||
```
|
||||
|
||||
### Building in a container
|
||||
|
||||
Building in a container is simpler, but more restrictive:
|
||||
|
||||
- It requires the `podman` command and the ability to run Linux containers
|
||||
- The created executable is a Linux executable, and depends on dynamic libraries
|
||||
which may only be available only in a container of a similar Linux
|
||||
distribution.
|
||||
|
||||
```bash
|
||||
$ make binary # Or (make all) to also build documentation, see below.
|
||||
```
|
||||
|
||||
To build a pure-Go static binary (disables devicemapper, btrfs, and gpgme):
|
||||
|
||||
```bash
|
||||
$ make binary-static DISABLE_CGO=1
|
||||
```
|
||||
By default the `make` command (make all) will build bin/skopeo and the documentation locally.
|
||||
|
||||
### Building documentation
|
||||
|
||||
@@ -136,18 +174,31 @@ To build the manual you will need go-md2man.
|
||||
|
||||
```bash
|
||||
# Debian:
|
||||
$ sudo apt-get install go-md2man
|
||||
sudo apt-get install go-md2man
|
||||
```
|
||||
|
||||
```
|
||||
# Fedora:
|
||||
$ sudo dnf install go-md2man
|
||||
sudo dnf install go-md2man
|
||||
```
|
||||
|
||||
Then
|
||||
|
||||
```bash
|
||||
$ make docs
|
||||
make docs
|
||||
```
|
||||
|
||||
### Building in a container
|
||||
|
||||
Building in a container is simpler, but more restrictive:
|
||||
|
||||
- It requires the `podman` command and the ability to run Linux containers.
|
||||
- The created executable is a Linux executable, and depends on dynamic libraries
|
||||
which may only be available only in a container of a similar Linux
|
||||
distribution.
|
||||
|
||||
```bash
|
||||
$ make binary
|
||||
```
|
||||
|
||||
### Installation
|
||||
@@ -155,5 +206,5 @@ $ make docs
|
||||
Finally, after the binary and documentation is built:
|
||||
|
||||
```bash
|
||||
$ sudo make install
|
||||
sudo make install
|
||||
```
|
||||
|
||||
@@ -30,18 +30,11 @@ type SkopeoSuite struct {
|
||||
func (s *SkopeoSuite) SetUpSuite(c *check.C) {
|
||||
_, err := exec.LookPath(skopeoBinary)
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *SkopeoSuite) TearDownSuite(c *check.C) {
|
||||
|
||||
}
|
||||
|
||||
func (s *SkopeoSuite) SetUpTest(c *check.C) {
|
||||
s.regV2 = setupRegistryV2At(c, privateRegistryURL0, false, false)
|
||||
s.regV2WithAuth = setupRegistryV2At(c, privateRegistryURL1, true, false)
|
||||
}
|
||||
|
||||
func (s *SkopeoSuite) TearDownTest(c *check.C) {
|
||||
func (s *SkopeoSuite) TearDownSuite(c *check.C) {
|
||||
if s.regV2 != nil {
|
||||
s.regV2.Close()
|
||||
}
|
||||
@@ -71,7 +64,7 @@ func (s *SkopeoSuite) TestNeedAuthToPrivateRegistryV2WithoutDockerCfg(c *check.C
|
||||
}
|
||||
|
||||
func (s *SkopeoSuite) TestCertDirInsteadOfCertPath(c *check.C) {
|
||||
wanted := ".*flag provided but not defined: -cert-path.*"
|
||||
wanted := ".*unknown flag: --cert-path.*"
|
||||
assertSkopeoFails(c, wanted, "--tls-verify=false", "inspect", fmt.Sprintf("docker://%s/busybox:latest", s.regV2WithAuth.url), "--cert-path=/")
|
||||
wanted = ".*unauthorized: authentication required.*"
|
||||
assertSkopeoFails(c, wanted, "--tls-verify=false", "inspect", fmt.Sprintf("docker://%s/busybox:latest", s.regV2WithAuth.url), "--cert-dir=/etc/docker/certs.d/")
|
||||
@@ -91,3 +84,30 @@ func (s *SkopeoSuite) TestNoNeedAuthToPrivateRegistryV2ImageNotFound(c *check.C)
|
||||
func (s *SkopeoSuite) TestInspectFailsWhenReferenceIsInvalid(c *check.C) {
|
||||
assertSkopeoFails(c, `.*Invalid image name.*`, "inspect", "unknown")
|
||||
}
|
||||
|
||||
func (s *SkopeoSuite) TestLoginLogout(c *check.C) {
|
||||
wanted := "^Login Succeeded!\n$"
|
||||
assertSkopeoSucceeds(c, wanted, "login", "--tls-verify=false", "--username="+s.regV2WithAuth.username, "--password="+s.regV2WithAuth.password, s.regV2WithAuth.url)
|
||||
// test --get-login returns username
|
||||
wanted = fmt.Sprintf("^%s\n$", s.regV2WithAuth.username)
|
||||
assertSkopeoSucceeds(c, wanted, "login", "--tls-verify=false", "--get-login", s.regV2WithAuth.url)
|
||||
// test logout
|
||||
wanted = fmt.Sprintf("^Removed login credentials for %s\n$", s.regV2WithAuth.url)
|
||||
assertSkopeoSucceeds(c, wanted, "logout", s.regV2WithAuth.url)
|
||||
}
|
||||
|
||||
func (s *SkopeoSuite) TestCopyWithLocalAuth(c *check.C) {
|
||||
wanted := "^Login Succeeded!\n$"
|
||||
assertSkopeoSucceeds(c, wanted, "login", "--tls-verify=false", "--username="+s.regV2WithAuth.username, "--password="+s.regV2WithAuth.password, s.regV2WithAuth.url)
|
||||
// copy to private registry using local authentication
|
||||
imageName := fmt.Sprintf("docker://%s/busybox:mine", s.regV2WithAuth.url)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--dest-tls-verify=false", testFQIN+":latest", imageName)
|
||||
// inspect from private registry
|
||||
assertSkopeoSucceeds(c, "", "inspect", "--tls-verify=false", imageName)
|
||||
// logout from the registry
|
||||
wanted = fmt.Sprintf("^Removed login credentials for %s\n$", s.regV2WithAuth.url)
|
||||
assertSkopeoSucceeds(c, wanted, "logout", s.regV2WithAuth.url)
|
||||
// inspect from private registry should fail after logout
|
||||
wanted = ".*unauthorized: authentication required.*"
|
||||
assertSkopeoFails(c, wanted, "inspect", "--tls-verify=false", imageName)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ const (
|
||||
v2DockerRegistryURL = "localhost:5555" // Update also policy.json
|
||||
v2s1DockerRegistryURL = "localhost:5556"
|
||||
knownWindowsOnlyImage = "docker://mcr.microsoft.com/windows/nanoserver:1909"
|
||||
knownListImageRepo = "docker://registry.fedoraproject.org/fedora-minimal"
|
||||
knownListImage = knownListImageRepo + ":38"
|
||||
)
|
||||
|
||||
type CopySuite struct {
|
||||
@@ -99,14 +101,14 @@ func (s *CopySuite) TestCopyWithManifestList(c *check.C) {
|
||||
dir, err := ioutil.TempDir("", "copy-manifest-list")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir)
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox:latest", "dir:"+dir)
|
||||
assertSkopeoSucceeds(c, "", "copy", knownListImage, "dir:"+dir)
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopyAllWithManifestList(c *check.C) {
|
||||
dir, err := ioutil.TempDir("", "copy-all-manifest-list")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "docker://estesp/busybox:latest", "dir:"+dir)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", knownListImage, "dir:"+dir)
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopyAllWithManifestListRoundTrip(c *check.C) {
|
||||
@@ -122,7 +124,7 @@ func (s *CopySuite) TestCopyAllWithManifestListRoundTrip(c *check.C) {
|
||||
dir2, err := ioutil.TempDir("", "copy-all-manifest-list-dir")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "docker://estesp/busybox:latest", "oci:"+oci1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", knownListImage, "oci:"+oci1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci1, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "dir:"+dir1, "oci:"+oci2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci2, "dir:"+dir2)
|
||||
@@ -144,9 +146,9 @@ func (s *CopySuite) TestCopyAllWithManifestListConverge(c *check.C) {
|
||||
dir2, err := ioutil.TempDir("", "copy-all-manifest-list-dir")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "docker://estesp/busybox:latest", "oci:"+oci1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", knownListImage, "oci:"+oci1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci1, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "--format", "oci", "docker://estesp/busybox:latest", "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "--format", "oci", knownListImage, "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "dir:"+dir2, "oci:"+oci2)
|
||||
assertDirImagesAreEqual(c, dir1, dir2)
|
||||
out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2)
|
||||
@@ -166,9 +168,9 @@ func (s *CopySuite) TestCopyWithManifestListConverge(c *check.C) {
|
||||
dir2, err := ioutil.TempDir("", "copy-all-manifest-list-dir")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox:latest", "oci:"+oci1)
|
||||
assertSkopeoSucceeds(c, "", "copy", knownListImage, "oci:"+oci1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci1, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--format", "oci", "docker://estesp/busybox:latest", "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--format", "oci", knownListImage, "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "dir:"+dir2, "oci:"+oci2)
|
||||
assertDirImagesAreEqual(c, dir1, dir2)
|
||||
out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2)
|
||||
@@ -180,7 +182,7 @@ func (s *CopySuite) TestCopyAllWithManifestListStorageFails(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(storage)
|
||||
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
|
||||
assertSkopeoFails(c, `.*destination transport .* does not support copying multiple images as a group.*`, "copy", "--all", "docker://estesp/busybox:latest", "containers-storage:"+storage+"test")
|
||||
assertSkopeoFails(c, `.*destination transport .* does not support copying multiple images as a group.*`, "copy", "--all", knownListImage, "containers-storage:"+storage+"test")
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopyWithManifestListStorage(c *check.C) {
|
||||
@@ -194,8 +196,8 @@ func (s *CopySuite) TestCopyWithManifestListStorage(c *check.C) {
|
||||
dir2, err := ioutil.TempDir("", "copy-manifest-list-storage-dir")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox:latest", "containers-storage:"+storage+"test")
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox:latest", "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", knownListImage, "containers-storage:"+storage+"test")
|
||||
assertSkopeoSucceeds(c, "", "copy", knownListImage, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "containers-storage:"+storage+"test", "dir:"+dir2)
|
||||
runDecompressDirs(c, "", dir1, dir2)
|
||||
assertDirImagesAreEqual(c, dir1, dir2)
|
||||
@@ -212,9 +214,9 @@ func (s *CopySuite) TestCopyWithManifestListStorageMultiple(c *check.C) {
|
||||
dir2, err := ioutil.TempDir("", "copy-manifest-list-storage-multiple-dir")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir2)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch", "amd64", "copy", "docker://estesp/busybox:latest", "containers-storage:"+storage+"test")
|
||||
assertSkopeoSucceeds(c, "", "--override-arch", "arm64", "copy", "docker://estesp/busybox:latest", "containers-storage:"+storage+"test")
|
||||
assertSkopeoSucceeds(c, "", "--override-arch", "arm64", "copy", "docker://estesp/busybox:latest", "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch", "amd64", "copy", knownListImage, "containers-storage:"+storage+"test")
|
||||
assertSkopeoSucceeds(c, "", "--override-arch", "arm64", "copy", knownListImage, "containers-storage:"+storage+"test")
|
||||
assertSkopeoSucceeds(c, "", "--override-arch", "arm64", "copy", knownListImage, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "containers-storage:"+storage+"test", "dir:"+dir2)
|
||||
runDecompressDirs(c, "", dir1, dir2)
|
||||
assertDirImagesAreEqual(c, dir1, dir2)
|
||||
@@ -233,18 +235,33 @@ func (s *CopySuite) TestCopyWithManifestListDigest(c *check.C) {
|
||||
oci2, err := ioutil.TempDir("", "copy-manifest-list-digest-oci")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(oci2)
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", "docker://estesp/busybox:latest")
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", knownListImage)
|
||||
manifestDigest, err := manifest.Digest([]byte(m))
|
||||
c.Assert(err, check.IsNil)
|
||||
digest := manifestDigest.String()
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox@"+digest, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "docker://estesp/busybox@"+digest, "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", knownListImageRepo+"@"+digest, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", knownListImageRepo+"@"+digest, "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "dir:"+dir1, "oci:"+oci1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "dir:"+dir2, "oci:"+oci2)
|
||||
out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2)
|
||||
c.Assert(out, check.Equals, "")
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopyWithDigestfileOutput(c *check.C) {
|
||||
tempdir, err := ioutil.TempDir("", "tempdir")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(tempdir)
|
||||
dir1, err := ioutil.TempDir("", "copy-manifest-list-digest-dir")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir1)
|
||||
digestOutPath := filepath.Join(tempdir, "digest.txt")
|
||||
assertSkopeoSucceeds(c, "", "copy", "--digestfile="+digestOutPath, knownListImage, "dir:"+dir1)
|
||||
readDigest, err := ioutil.ReadFile(digestOutPath)
|
||||
c.Assert(err, check.IsNil)
|
||||
_, err = digest.Parse(string(readDigest))
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopyWithManifestListStorageDigest(c *check.C) {
|
||||
storage, err := ioutil.TempDir("", "copy-manifest-list-storage-digest")
|
||||
c.Assert(err, check.IsNil)
|
||||
@@ -256,13 +273,13 @@ func (s *CopySuite) TestCopyWithManifestListStorageDigest(c *check.C) {
|
||||
dir2, err := ioutil.TempDir("", "copy-manifest-list-storage-digest-dir")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir2)
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", "docker://estesp/busybox:latest")
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", knownListImage)
|
||||
manifestDigest, err := manifest.Digest([]byte(m))
|
||||
c.Assert(err, check.IsNil)
|
||||
digest := manifestDigest.String()
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "copy", "containers-storage:"+storage+"test@"+digest, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox@"+digest, "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", knownListImageRepo+"@"+digest, "dir:"+dir2)
|
||||
runDecompressDirs(c, "", dir1, dir2)
|
||||
assertDirImagesAreEqual(c, dir1, dir2)
|
||||
}
|
||||
@@ -278,13 +295,13 @@ func (s *CopySuite) TestCopyWithManifestListStorageDigestMultipleArches(c *check
|
||||
dir2, err := ioutil.TempDir("", "copy-manifest-list-storage-digest-dir")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir2)
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", "docker://estesp/busybox:latest")
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", knownListImage)
|
||||
manifestDigest, err := manifest.Digest([]byte(m))
|
||||
c.Assert(err, check.IsNil)
|
||||
digest := manifestDigest.String()
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "copy", "containers-storage:"+storage+"test@"+digest, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox@"+digest, "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", knownListImageRepo+"@"+digest, "dir:"+dir2)
|
||||
runDecompressDirs(c, "", dir1, dir2)
|
||||
assertDirImagesAreEqual(c, dir1, dir2)
|
||||
}
|
||||
@@ -294,14 +311,14 @@ func (s *CopySuite) TestCopyWithManifestListStorageDigestMultipleArchesBothUseLi
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(storage)
|
||||
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", "docker://estesp/busybox:latest")
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", knownListImage)
|
||||
manifestDigest, err := manifest.Digest([]byte(m))
|
||||
c.Assert(err, check.IsNil)
|
||||
digest := manifestDigest.String()
|
||||
_, err = manifest.ListFromBlob([]byte(m), manifest.GuessMIMEType([]byte(m)))
|
||||
c.Assert(err, check.IsNil)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=amd64", "copy", "docker://estesp/busybox@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=arm64", "copy", "docker://estesp/busybox@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=amd64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=arm64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoFails(c, `.*error reading manifest for image instance.*does not exist.*`, "--override-arch=amd64", "inspect", "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoFails(c, `.*error reading manifest for image instance.*does not exist.*`, "--override-arch=amd64", "inspect", "--config", "containers-storage:"+storage+"test@"+digest)
|
||||
i2 := combinedOutputOfCommand(c, skopeoBinary, "--override-arch=arm64", "inspect", "--config", "containers-storage:"+storage+"test@"+digest)
|
||||
@@ -316,7 +333,7 @@ func (s *CopySuite) TestCopyWithManifestListStorageDigestMultipleArchesFirstUses
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(storage)
|
||||
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", "docker://estesp/busybox:latest")
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", knownListImage)
|
||||
manifestDigest, err := manifest.Digest([]byte(m))
|
||||
c.Assert(err, check.IsNil)
|
||||
digest := manifestDigest.String()
|
||||
@@ -326,8 +343,8 @@ func (s *CopySuite) TestCopyWithManifestListStorageDigestMultipleArchesFirstUses
|
||||
c.Assert(err, check.IsNil)
|
||||
arm64Instance, err := list.ChooseInstance(&types.SystemContext{ArchitectureChoice: "arm64"})
|
||||
c.Assert(err, check.IsNil)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=amd64", "copy", "docker://estesp/busybox@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=arm64", "copy", "docker://estesp/busybox@"+arm64Instance.String(), "containers-storage:"+storage+"test@"+arm64Instance.String())
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=amd64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=arm64", "copy", knownListImageRepo+"@"+arm64Instance.String(), "containers-storage:"+storage+"test@"+arm64Instance.String())
|
||||
i1 := combinedOutputOfCommand(c, skopeoBinary, "--override-arch=amd64", "inspect", "--config", "containers-storage:"+storage+"test@"+digest)
|
||||
var image1 imgspecv1.Image
|
||||
err = json.Unmarshal([]byte(i1), &image1)
|
||||
@@ -352,7 +369,7 @@ func (s *CopySuite) TestCopyWithManifestListStorageDigestMultipleArchesSecondUse
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(storage)
|
||||
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", "docker://estesp/busybox:latest")
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", knownListImage)
|
||||
manifestDigest, err := manifest.Digest([]byte(m))
|
||||
c.Assert(err, check.IsNil)
|
||||
digest := manifestDigest.String()
|
||||
@@ -362,8 +379,8 @@ func (s *CopySuite) TestCopyWithManifestListStorageDigestMultipleArchesSecondUse
|
||||
c.Assert(err, check.IsNil)
|
||||
arm64Instance, err := list.ChooseInstance(&types.SystemContext{ArchitectureChoice: "arm64"})
|
||||
c.Assert(err, check.IsNil)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=amd64", "copy", "docker://estesp/busybox@"+amd64Instance.String(), "containers-storage:"+storage+"test@"+amd64Instance.String())
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=arm64", "copy", "docker://estesp/busybox@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=amd64", "copy", knownListImageRepo+"@"+amd64Instance.String(), "containers-storage:"+storage+"test@"+amd64Instance.String())
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=arm64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
i1 := combinedOutputOfCommand(c, skopeoBinary, "--override-arch=amd64", "inspect", "--config", "containers-storage:"+storage+"test@"+amd64Instance.String())
|
||||
var image1 imgspecv1.Image
|
||||
err = json.Unmarshal([]byte(i1), &image1)
|
||||
@@ -388,7 +405,7 @@ func (s *CopySuite) TestCopyWithManifestListStorageDigestMultipleArchesThirdUses
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(storage)
|
||||
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", "docker://estesp/busybox:latest")
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", knownListImage)
|
||||
manifestDigest, err := manifest.Digest([]byte(m))
|
||||
c.Assert(err, check.IsNil)
|
||||
digest := manifestDigest.String()
|
||||
@@ -398,9 +415,9 @@ func (s *CopySuite) TestCopyWithManifestListStorageDigestMultipleArchesThirdUses
|
||||
c.Assert(err, check.IsNil)
|
||||
arm64Instance, err := list.ChooseInstance(&types.SystemContext{ArchitectureChoice: "arm64"})
|
||||
c.Assert(err, check.IsNil)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=amd64", "copy", "docker://estesp/busybox@"+amd64Instance.String(), "containers-storage:"+storage+"test@"+amd64Instance.String())
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=amd64", "copy", "docker://estesp/busybox@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=arm64", "copy", "docker://estesp/busybox@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=amd64", "copy", knownListImageRepo+"@"+amd64Instance.String(), "containers-storage:"+storage+"test@"+amd64Instance.String())
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=amd64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=arm64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoFails(c, `.*error reading manifest for image instance.*does not exist.*`, "--override-arch=amd64", "inspect", "--config", "containers-storage:"+storage+"test@"+digest)
|
||||
i1 := combinedOutputOfCommand(c, skopeoBinary, "--override-arch=amd64", "inspect", "--config", "containers-storage:"+storage+"test@"+amd64Instance.String())
|
||||
var image1 imgspecv1.Image
|
||||
@@ -424,7 +441,7 @@ func (s *CopySuite) TestCopyWithManifestListStorageDigestMultipleArchesTagAndDig
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(storage)
|
||||
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", "docker://estesp/busybox:latest")
|
||||
m := combinedOutputOfCommand(c, skopeoBinary, "inspect", "--raw", knownListImage)
|
||||
manifestDigest, err := manifest.Digest([]byte(m))
|
||||
c.Assert(err, check.IsNil)
|
||||
digest := manifestDigest.String()
|
||||
@@ -434,8 +451,8 @@ func (s *CopySuite) TestCopyWithManifestListStorageDigestMultipleArchesTagAndDig
|
||||
c.Assert(err, check.IsNil)
|
||||
arm64Instance, err := list.ChooseInstance(&types.SystemContext{ArchitectureChoice: "arm64"})
|
||||
c.Assert(err, check.IsNil)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=amd64", "copy", "docker://estesp/busybox:latest", "containers-storage:"+storage+"test:latest")
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=arm64", "copy", "docker://estesp/busybox@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=amd64", "copy", knownListImage, "containers-storage:"+storage+"test:latest")
|
||||
assertSkopeoSucceeds(c, "", "--override-arch=arm64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
|
||||
assertSkopeoFails(c, `.*error reading manifest for image instance.*does not exist.*`, "--override-arch=amd64", "inspect", "--config", "containers-storage:"+storage+"test@"+digest)
|
||||
i1 := combinedOutputOfCommand(c, skopeoBinary, "--override-arch=arm64", "inspect", "--config", "containers-storage:"+storage+"test:latest")
|
||||
var image1 imgspecv1.Image
|
||||
@@ -464,16 +481,16 @@ func (s *CopySuite) TestCopyWithManifestListStorageDigestMultipleArchesTagAndDig
|
||||
c.Assert(image5.Architecture, check.Equals, "arm64")
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopyFailsWhenImageOSDoesntMatchRuntimeOS(c *check.C) {
|
||||
storage, err := ioutil.TempDir("", "copy-fails-image-doesnt-match-runtime")
|
||||
func (s *CopySuite) TestCopyFailsWhenImageOSDoesNotMatchRuntimeOS(c *check.C) {
|
||||
storage, err := ioutil.TempDir("", "copy-fails-image-does-not-match-runtime")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(storage)
|
||||
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
|
||||
assertSkopeoFails(c, `.*no image found in manifest list for architecture .*, variant .*, OS .*`, "copy", knownWindowsOnlyImage, "containers-storage:"+storage+"test")
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopySucceedsWhenImageDoesntMatchRuntimeButWeOverride(c *check.C) {
|
||||
storage, err := ioutil.TempDir("", "copy-succeeds-image-doesnt-match-runtime-but-override")
|
||||
func (s *CopySuite) TestCopySucceedsWhenImageDoesNotMatchRuntimeButWeOverride(c *check.C) {
|
||||
storage, err := ioutil.TempDir("", "copy-succeeds-image-does-not-match-runtime-but-override")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(storage)
|
||||
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
|
||||
@@ -488,14 +505,14 @@ func (s *CopySuite) TestCopySimpleAtomicRegistry(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir2)
|
||||
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of neeeding an Internet connection.
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
|
||||
// "pull": docker: → dir:
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox:amd64", "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", testFQIN64, "dir:"+dir1)
|
||||
// "push": dir: → atomic:
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--debug", "copy", "dir:"+dir1, "atomic:localhost:5000/myns/unsigned:unsigned")
|
||||
// The result of pushing and pulling is an equivalent image, except for schema1 embedded names.
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "atomic:localhost:5000/myns/unsigned:unsigned", "dir:"+dir2)
|
||||
assertSchema1DirImagesAreEqualExceptNames(c, dir1, "estesp/busybox:amd64", dir2, "myns/unsigned:unsigned")
|
||||
assertSchema1DirImagesAreEqualExceptNames(c, dir1, "libpod/busybox:amd64", dir2, "myns/unsigned:unsigned")
|
||||
}
|
||||
|
||||
// The most basic (skopeo copy) use:
|
||||
@@ -509,30 +526,30 @@ func (s *CopySuite) TestCopySimple(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir2)
|
||||
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of neeeding an Internet connection.
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
|
||||
// "pull": docker: → dir:
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://busybox", "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://registry.k8s.io/pause", "dir:"+dir1)
|
||||
// "push": dir: → docker(v2s2):
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--debug", "copy", "dir:"+dir1, ourRegistry+"busybox:unsigned")
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--debug", "copy", "dir:"+dir1, ourRegistry+"pause:unsigned")
|
||||
// The result of pushing and pulling is an unmodified image.
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", ourRegistry+"busybox:unsigned", "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", ourRegistry+"pause:unsigned", "dir:"+dir2)
|
||||
out := combinedOutputOfCommand(c, "diff", "-urN", dir1, dir2)
|
||||
c.Assert(out, check.Equals, "")
|
||||
|
||||
// docker v2s2 -> OCI image layout with image name
|
||||
// ociDest will be created by oci: if it doesn't exist
|
||||
// so don't create it here to exercise auto-creation
|
||||
ociDest := "busybox-latest-image"
|
||||
ociImgName := "busybox"
|
||||
ociDest := "pause-latest-image"
|
||||
ociImgName := "pause"
|
||||
defer os.RemoveAll(ociDest)
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://busybox:latest", "oci:"+ociDest+":"+ociImgName)
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://registry.k8s.io/pause:latest", "oci:"+ociDest+":"+ociImgName)
|
||||
_, err = os.Stat(ociDest)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// docker v2s2 -> OCI image layout without image name
|
||||
ociDest = "busybox-latest-noimage"
|
||||
ociDest = "pause-latest-noimage"
|
||||
defer os.RemoveAll(ociDest)
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://busybox:latest", "oci:"+ociDest)
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://registry.k8s.io/pause:latest", "oci:"+ociDest)
|
||||
_, err = os.Stat(ociDest)
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
@@ -586,7 +603,7 @@ func (s *CopySuite) TestCopyEncryption(c *check.C) {
|
||||
"oci:"+encryptedImgDir+":encrypted", "oci:"+decryptedImgDir+":decrypted")
|
||||
|
||||
// Copy a standard busybox image locally
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://busybox:1.31.1", "oci:"+originalImageDir+":latest")
|
||||
assertSkopeoSucceeds(c, "", "copy", testFQIN+":1.30.1", "oci:"+originalImageDir+":latest")
|
||||
|
||||
// Encrypt the image
|
||||
assertSkopeoSucceeds(c, "", "copy", "--encryption-key",
|
||||
@@ -617,7 +634,7 @@ func (s *CopySuite) TestCopyEncryption(c *check.C) {
|
||||
matchLayerBlobBinaryType(c, decryptedImgDir+"/blobs/sha256", "application/x-gzip", 1)
|
||||
|
||||
// Copy a standard multi layer nginx image locally
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://nginx:1.17.8", "oci:"+multiLayerImageDir+":latest")
|
||||
assertSkopeoSucceeds(c, "", "copy", testFQINMultiLayer, "oci:"+multiLayerImageDir+":latest")
|
||||
|
||||
// Partially encrypt the image
|
||||
assertSkopeoSucceeds(c, "", "copy", "--encryption-key", "jwe:"+keysDir+"/public.key",
|
||||
@@ -626,7 +643,7 @@ func (s *CopySuite) TestCopyEncryption(c *check.C) {
|
||||
// Since the image is partially encrypted we should find layers that aren't encrypted
|
||||
matchLayerBlobBinaryType(c, partiallyEncryptedImgDir+"/blobs/sha256", "application/x-gzip", 2)
|
||||
|
||||
// Decrypt the partically encrypted image
|
||||
// Decrypt the partially encrypted image
|
||||
assertSkopeoSucceeds(c, "", "copy", "--decryption-key", keysDir+"/private.key",
|
||||
"oci:"+partiallyEncryptedImgDir+":encrypted", "oci:"+partiallyDecryptedImgDir+":decrypted")
|
||||
|
||||
@@ -720,13 +737,13 @@ func (s *CopySuite) TestCopyStreaming(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir2)
|
||||
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of neeeding an Internet connection.
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
|
||||
// streaming: docker: → atomic:
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--debug", "copy", "docker://estesp/busybox:amd64", "atomic:localhost:5000/myns/unsigned:streaming")
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--debug", "copy", testFQIN64, "atomic:localhost:5000/myns/unsigned:streaming")
|
||||
// Compare (copies of) the original and the copy:
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox:amd64", "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", testFQIN64, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "atomic:localhost:5000/myns/unsigned:streaming", "dir:"+dir2)
|
||||
assertSchema1DirImagesAreEqualExceptNames(c, dir1, "estesp/busybox:amd64", dir2, "myns/unsigned:streaming")
|
||||
assertSchema1DirImagesAreEqualExceptNames(c, dir1, "libpod/busybox:amd64", dir2, "myns/unsigned:streaming")
|
||||
// FIXME: Also check pushing to docker://
|
||||
}
|
||||
|
||||
@@ -746,7 +763,7 @@ func (s *CopySuite) TestCopyOCIRoundTrip(c *check.C) {
|
||||
defer os.RemoveAll(oci2)
|
||||
|
||||
// Docker -> OCI
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--debug", "copy", "docker://busybox", "oci:"+oci1+":latest")
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--debug", "copy", testFQIN, "oci:"+oci1+":latest")
|
||||
// OCI -> Docker
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--debug", "copy", "oci:"+oci1+":latest", ourRegistry+"original/busybox:oci_copy")
|
||||
// Docker -> OCI
|
||||
@@ -797,16 +814,16 @@ func (s *CopySuite) TestCopySignatures(c *check.C) {
|
||||
defer os.Remove(policy)
|
||||
|
||||
// type: reject
|
||||
assertSkopeoFails(c, ".*Source image rejected: Running image docker://busybox:latest is rejected by policy.*",
|
||||
"--policy", policy, "copy", "docker://busybox:latest", dirDest)
|
||||
assertSkopeoFails(c, fmt.Sprintf(".*Source image rejected: Running image %s:latest is rejected by policy.*", testFQIN),
|
||||
"--policy", policy, "copy", testFQIN+":latest", dirDest)
|
||||
|
||||
// type: insecureAcceptAnything
|
||||
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "docker://openshift/hello-openshift", dirDest)
|
||||
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "docker://quay.io/openshift/origin-hello-openshift", dirDest)
|
||||
|
||||
// type: signedBy
|
||||
// Sign the images
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "--sign-by", "personal@example.com", "docker://busybox:1.26", "atomic:localhost:5006/myns/personal:personal")
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "--sign-by", "official@example.com", "docker://busybox:1.26.1", "atomic:localhost:5006/myns/official:official")
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "--sign-by", "personal@example.com", testFQIN+":1.26", "atomic:localhost:5006/myns/personal:personal")
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "--sign-by", "official@example.com", testFQIN+":1.26.1", "atomic:localhost:5006/myns/official:official")
|
||||
// Verify that we can pull them
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--policy", policy, "copy", "atomic:localhost:5006/myns/personal:personal", dirDest)
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--policy", policy, "copy", "atomic:localhost:5006/myns/official:official", dirDest)
|
||||
@@ -860,10 +877,10 @@ func (s *CopySuite) TestCopyDirSignatures(c *check.C) {
|
||||
defer os.Remove(policy)
|
||||
|
||||
// Get some images.
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox:armfh", topDirDest+"/dir1")
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox:s390x", topDirDest+"/dir2")
|
||||
assertSkopeoSucceeds(c, "", "copy", testFQIN+":armfh", topDirDest+"/dir1")
|
||||
assertSkopeoSucceeds(c, "", "copy", testFQIN+":s390x", topDirDest+"/dir2")
|
||||
|
||||
// Sign the images. By coping fom a topDirDest/dirN, also test that non-/restricted paths
|
||||
// Sign the images. By coping from a topDirDest/dirN, also test that non-/restricted paths
|
||||
// use the dir:"" default of insecureAcceptAnything.
|
||||
// (For signing, we must push to atomic: to get a Docker identity to use in the signature.)
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--policy", policy, "copy", "--sign-by", "personal@example.com", topDirDest+"/dir1", "atomic:localhost:5000/myns/personal:dirstaging")
|
||||
@@ -977,7 +994,7 @@ func (s *CopySuite) TestCopyDockerSigstore(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// Get an image to work with. Also verifies that we can use Docker repositories with no sigstore configured.
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--registries.d", registriesDir, "copy", "docker://busybox", ourRegistry+"original/busybox")
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--registries.d", registriesDir, "copy", testFQIN, ourRegistry+"original/busybox")
|
||||
// Pulling an unsigned image fails.
|
||||
assertSkopeoFails(c, ".*Source image rejected: A signature was required, but no signature exists.*",
|
||||
"--tls-verify=false", "--policy", policy, "--registries.d", registriesDir, "copy", ourRegistry+"original/busybox", dirDest)
|
||||
@@ -1031,7 +1048,7 @@ func (s *CopySuite) TestCopyAtomicExtension(c *check.C) {
|
||||
defer os.Remove(policy)
|
||||
|
||||
// Get an image to work with to an atomic: destination. Also verifies that we can use Docker repositories without X-Registry-Supports-Signatures
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--registries.d", registriesDir, "copy", "docker://busybox", "atomic:localhost:5000/myns/extension:unsigned")
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--registries.d", registriesDir, "copy", testFQIN, "atomic:localhost:5000/myns/extension:unsigned")
|
||||
// Pulling an unsigned image using atomic: fails.
|
||||
assertSkopeoFails(c, ".*Source image rejected: A signature was required, but no signature exists.*",
|
||||
"--tls-verify=false", "--policy", policy,
|
||||
@@ -1055,7 +1072,7 @@ func (s *CopySuite) TestCopyAtomicExtension(c *check.C) {
|
||||
|
||||
// Get another image (different so that they don't share signatures, and sign it using docker://)
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--registries.d", registriesDir,
|
||||
"copy", "--sign-by", "personal@example.com", "docker://estesp/busybox:ppc64le", "atomic:localhost:5000/myns/extension:extension")
|
||||
"copy", "--sign-by", "personal@example.com", testFQIN+":ppc64le", "docker://localhost:5000/myns/extension:extension")
|
||||
c.Logf("%s", combinedOutputOfCommand(c, "oc", "get", "istag", "extension:extension", "-o", "json"))
|
||||
// Pulling the image using atomic: succeeds.
|
||||
assertSkopeoSucceeds(c, "", "--debug", "--tls-verify=false", "--policy", policy,
|
||||
@@ -1067,8 +1084,98 @@ func (s *CopySuite) TestCopyAtomicExtension(c *check.C) {
|
||||
assertDirImagesAreEqual(c, filepath.Join(topDir, "dirDA"), filepath.Join(topDir, "dirDD"))
|
||||
}
|
||||
|
||||
// copyWithSignedIdentity creates a copy of an unsigned image, adding a signature for an unrelated identity
|
||||
// This should be easier than using standalone-sign.
|
||||
func copyWithSignedIdentity(c *check.C, src, dest, signedIdentity, signBy, registriesDir string) {
|
||||
topDir, err := ioutil.TempDir("", "copyWithSignedIdentity")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(topDir)
|
||||
|
||||
signingDir := filepath.Join(topDir, "signing-temp")
|
||||
assertSkopeoSucceeds(c, "", "copy", "--src-tls-verify=false", src, "dir:"+signingDir)
|
||||
// Unknown error in Travis: https://github.com/containers/skopeo/issues/1093
|
||||
// c.Logf("%s", combinedOutputOfCommand(c, "ls", "-laR", signingDir))
|
||||
assertSkopeoSucceeds(c, "^$", "standalone-sign", "-o", filepath.Join(signingDir, "signature-1"),
|
||||
filepath.Join(signingDir, "manifest.json"), signedIdentity, signBy)
|
||||
// Unknown error in Travis: https://github.com/containers/skopeo/issues/1093
|
||||
// c.Logf("%s", combinedOutputOfCommand(c, "ls", "-laR", signingDir))
|
||||
assertSkopeoSucceeds(c, "", "--registries.d", registriesDir, "copy", "--dest-tls-verify=false", "dir:"+signingDir, dest)
|
||||
}
|
||||
|
||||
// Both mirroring support in registries.conf, and mirrored remapIdentity support in policy.json
|
||||
func (s *CopySuite) TestCopyVerifyingMirroredSignatures(c *check.C) {
|
||||
const regPrefix = "docker://localhost:5006/myns/mirroring-"
|
||||
|
||||
mech, _, err := signature.NewEphemeralGPGSigningMechanism([]byte{})
|
||||
c.Assert(err, check.IsNil)
|
||||
defer mech.Close()
|
||||
if err := mech.SupportsSigning(); err != nil { // FIXME? Test that verification and policy enforcement works, using signatures from fixtures
|
||||
c.Skip(fmt.Sprintf("Signing not supported: %v", err))
|
||||
}
|
||||
|
||||
topDir, err := ioutil.TempDir("", "mirrored-signatures")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(topDir)
|
||||
registriesDir := filepath.Join(topDir, "registries.d") // An empty directory to disable sigstore use
|
||||
dirDest := "dir:" + filepath.Join(topDir, "unused-dest")
|
||||
|
||||
policy := fileFromFixture(c, "fixtures/policy.json", map[string]string{"@keydir@": s.gpgHome})
|
||||
defer os.Remove(policy)
|
||||
|
||||
// We use X-R-S-S for this testing to avoid having to deal with the sigstores.
|
||||
// A downside is that OpenShift records signatures per image, so the error messages below
|
||||
// list all signatures for other tags used for the same image as well.
|
||||
// So, make sure to never create a signature that could be considered valid in a different part of the test (i.e. don't reuse tags).
|
||||
|
||||
// Get an image to work with.
|
||||
assertSkopeoSucceeds(c, "", "copy", "--dest-tls-verify=false", testFQIN, regPrefix+"primary:unsigned")
|
||||
// Verify that unsigned images are rejected
|
||||
assertSkopeoFails(c, ".*Source image rejected: A signature was required, but no signature exists.*",
|
||||
"--policy", policy, "--registries.d", registriesDir, "--registries-conf", "fixtures/registries.conf", "copy", "--src-tls-verify=false", regPrefix+"primary:unsigned", dirDest)
|
||||
// Sign the image for the primary location
|
||||
assertSkopeoSucceeds(c, "", "--registries.d", registriesDir, "copy", "--src-tls-verify=false", "--dest-tls-verify=false", "--sign-by", "personal@example.com", regPrefix+"primary:unsigned", regPrefix+"primary:direct")
|
||||
// Verify that a correctly signed image in the primary location is usable.
|
||||
assertSkopeoSucceeds(c, "", "--policy", policy, "--registries.d", registriesDir, "--registries-conf", "fixtures/registries.conf", "copy", "--src-tls-verify=false", regPrefix+"primary:direct", dirDest)
|
||||
|
||||
// Sign the image for the mirror
|
||||
assertSkopeoSucceeds(c, "", "--registries.d", registriesDir, "copy", "--src-tls-verify=false", "--dest-tls-verify=false", "--sign-by", "personal@example.com", regPrefix+"primary:unsigned", regPrefix+"mirror:mirror-signed")
|
||||
// Verify that a correctly signed image for the mirror is accessible using the mirror's reference
|
||||
assertSkopeoSucceeds(c, "", "--policy", policy, "--registries.d", registriesDir, "--registries-conf", "fixtures/registries.conf", "copy", "--src-tls-verify=false", regPrefix+"mirror:mirror-signed", dirDest)
|
||||
// … but verify that while it is accessible using the primary location redirecting to the mirror, …
|
||||
assertSkopeoSucceeds(c, "" /* no --policy */, "--registries-conf", "fixtures/registries.conf", "copy", "--src-tls-verify=false", regPrefix+"primary:mirror-signed", dirDest)
|
||||
// … verify it is NOT accessible when requiring a signature.
|
||||
assertSkopeoFails(c, ".*Source image rejected: None of the signatures were accepted, reasons: Signature for identity localhost:5006/myns/mirroring-primary:direct is not accepted; Signature for identity localhost:5006/myns/mirroring-mirror:mirror-signed is not accepted.*",
|
||||
"--policy", policy, "--registries.d", registriesDir, "--registries-conf", "fixtures/registries.conf", "copy", "--src-tls-verify=false", regPrefix+"primary:mirror-signed", dirDest)
|
||||
|
||||
// Create a signature for mirroring-primary:primary-signed without pushing there.
|
||||
copyWithSignedIdentity(c, regPrefix+"primary:unsigned", regPrefix+"mirror:primary-signed",
|
||||
"localhost:5006/myns/mirroring-primary:primary-signed", "personal@example.com",
|
||||
registriesDir)
|
||||
// Verify that a correctly signed image for the primary is accessible using the primary's reference
|
||||
assertSkopeoSucceeds(c, "", "--policy", policy, "--registries.d", registriesDir, "--registries-conf", "fixtures/registries.conf", "copy", "--src-tls-verify=false", regPrefix+"primary:primary-signed", dirDest)
|
||||
// … but verify that while it is accessible using the mirror location
|
||||
assertSkopeoSucceeds(c, "" /* no --policy */, "--registries-conf", "fixtures/registries.conf", "copy", "--src-tls-verify=false", regPrefix+"mirror:primary-signed", dirDest)
|
||||
// … verify it is NOT accessible when requiring a signature.
|
||||
assertSkopeoFails(c, ".*Source image rejected: None of the signatures were accepted, reasons: Signature for identity localhost:5006/myns/mirroring-primary:direct is not accepted; Signature for identity localhost:5006/myns/mirroring-mirror:mirror-signed is not accepted; Signature for identity localhost:5006/myns/mirroring-primary:primary-signed is not accepted.*",
|
||||
"--policy", policy, "--registries.d", registriesDir, "--registries-conf", "fixtures/registries.conf", "copy", "--src-tls-verify=false", regPrefix+"mirror:primary-signed", dirDest)
|
||||
|
||||
assertSkopeoSucceeds(c, "", "--registries.d", registriesDir, "--registries-conf", "fixtures/registries.conf", "copy", "--src-tls-verify=false", "--dest-tls-verify=false", regPrefix+"primary:unsigned", regPrefix+"remap:remapped")
|
||||
// Verify that while a remapIdentity image is accessible using the remapped (mirror) location
|
||||
assertSkopeoSucceeds(c, "" /* no --policy */, "--registries.d", registriesDir, "--registries-conf", "fixtures/registries.conf", "copy", "--src-tls-verify=false", regPrefix+"remap:remapped", dirDest)
|
||||
// … it is NOT accessible when requiring a signature …
|
||||
assertSkopeoFails(c, ".*Source image rejected: None of the signatures were accepted, reasons: Signature for identity localhost:5006/myns/mirroring-primary:direct is not accepted; Signature for identity localhost:5006/myns/mirroring-mirror:mirror-signed is not accepted; Signature for identity localhost:5006/myns/mirroring-primary:primary-signed is not accepted.*", "--policy", policy, "--registries.d", registriesDir, "--registries-conf", "fixtures/registries.conf", "copy", "--src-tls-verify=false", regPrefix+"remap:remapped", dirDest)
|
||||
// … until signed.
|
||||
copyWithSignedIdentity(c, regPrefix+"remap:remapped", regPrefix+"remap:remapped",
|
||||
"localhost:5006/myns/mirroring-primary:remapped", "personal@example.com",
|
||||
registriesDir)
|
||||
assertSkopeoSucceeds(c, "", "--policy", policy, "--registries.d", registriesDir, "--registries-conf", "fixtures/registries.conf", "copy", "--src-tls-verify=false", regPrefix+"remap:remapped", dirDest)
|
||||
// To be extra clear about the semantics, verify that the signedPrefix (primary) location never exists
|
||||
// and only the remapped prefix (mirror) is accessed.
|
||||
assertSkopeoFails(c, ".*Error initializing source docker://localhost:5006/myns/mirroring-primary:remapped:.*manifest unknown: manifest unknown.*", "--policy", policy, "--registries.d", registriesDir, "--registries-conf", "fixtures/registries.conf", "copy", "--src-tls-verify=false", regPrefix+"primary:remapped", dirDest)
|
||||
}
|
||||
|
||||
func (s *SkopeoSuite) TestCopySrcWithAuth(c *check.C) {
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "--dest-creds=testuser:testpassword", "docker://busybox", fmt.Sprintf("docker://%s/busybox:latest", s.regV2WithAuth.url))
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "--dest-creds=testuser:testpassword", testFQIN, fmt.Sprintf("docker://%s/busybox:latest", s.regV2WithAuth.url))
|
||||
dir1, err := ioutil.TempDir("", "copy-1")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir1)
|
||||
@@ -1076,15 +1183,15 @@ func (s *SkopeoSuite) TestCopySrcWithAuth(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *SkopeoSuite) TestCopyDestWithAuth(c *check.C) {
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "--dest-creds=testuser:testpassword", "docker://busybox", fmt.Sprintf("docker://%s/busybox:latest", s.regV2WithAuth.url))
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "--dest-creds=testuser:testpassword", testFQIN, fmt.Sprintf("docker://%s/busybox:latest", s.regV2WithAuth.url))
|
||||
}
|
||||
|
||||
func (s *SkopeoSuite) TestCopySrcAndDestWithAuth(c *check.C) {
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "--dest-creds=testuser:testpassword", "docker://busybox", fmt.Sprintf("docker://%s/busybox:latest", s.regV2WithAuth.url))
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "--dest-creds=testuser:testpassword", testFQIN, fmt.Sprintf("docker://%s/busybox:latest", s.regV2WithAuth.url))
|
||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "--src-creds=testuser:testpassword", "--dest-creds=testuser:testpassword", fmt.Sprintf("docker://%s/busybox:latest", s.regV2WithAuth.url), fmt.Sprintf("docker://%s/test:auth", s.regV2WithAuth.url))
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopyNoPanicOnHTTPResponseWOTLSVerifyFalse(c *check.C) {
|
||||
func (s *CopySuite) TestCopyNoPanicOnHTTPResponseWithoutTLSVerifyFalse(c *check.C) {
|
||||
const ourRegistry = "docker://" + v2DockerRegistryURL + "/"
|
||||
|
||||
// dir:test isn't created beforehand just because we already know this could
|
||||
@@ -1110,7 +1217,7 @@ func (s *CopySuite) TestCopyManifestConversion(c *check.C) {
|
||||
|
||||
// oci to v2s1 and vice-versa not supported yet
|
||||
// get v2s2 manifest type
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://busybox", "dir:"+srcDir)
|
||||
assertSkopeoSucceeds(c, "", "copy", testFQIN, "dir:"+srcDir)
|
||||
verifyManifestMIMEType(c, srcDir, manifest.DockerV2Schema2MediaType)
|
||||
// convert from v2s2 to oci
|
||||
assertSkopeoSucceeds(c, "", "copy", "--format=oci", "dir:"+srcDir, "dir:"+destDir1)
|
||||
@@ -1140,7 +1247,7 @@ func (s *CopySuite) testCopySchemaConversionRegistries(c *check.C, schema1Regist
|
||||
|
||||
// Ensure we are working with a schema2 image.
|
||||
// dir: accepts any manifest format, i.e. this makes …/input2 a schema2 source which cannot be asked to produce schema1 like ordinary docker: registries can.
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://busybox", "dir:"+input2Dir)
|
||||
assertSkopeoSucceeds(c, "", "copy", testFQIN, "dir:"+input2Dir)
|
||||
verifyManifestMIMEType(c, input2Dir, manifest.DockerV2Schema2MediaType)
|
||||
// 2→2 (the "f2t2" in tag means "from 2 to 2")
|
||||
assertSkopeoSucceeds(c, "", "copy", "--dest-tls-verify=false", "dir:"+input2Dir, schema2Registry+":f2t2")
|
||||
@@ -1160,14 +1267,6 @@ func (s *CopySuite) testCopySchemaConversionRegistries(c *check.C, schema1Regist
|
||||
verifyManifestMIMEType(c, destDir, manifest.DockerV2Schema1SignedMediaType)
|
||||
}
|
||||
|
||||
// Verify manifest in a dir: image at dir is expectedMIMEType.
|
||||
func verifyManifestMIMEType(c *check.C, dir string, expectedMIMEType string) {
|
||||
manifestBlob, err := ioutil.ReadFile(filepath.Join(dir, "manifest.json"))
|
||||
c.Assert(err, check.IsNil)
|
||||
mimeType := manifest.GuessMIMEType(manifestBlob)
|
||||
c.Assert(mimeType, check.Equals, expectedMIMEType)
|
||||
}
|
||||
|
||||
const regConfFixture = "./fixtures/registries.conf"
|
||||
|
||||
func (s *SkopeoSuite) TestSuccessCopySrcWithMirror(c *check.C) {
|
||||
|
||||
@@ -20,10 +20,41 @@
|
||||
"keyPath": "@keydir@/personal-pubkey.gpg"
|
||||
}
|
||||
],
|
||||
"localhost:5006/myns/mirroring-primary": [
|
||||
{
|
||||
"type": "signedBy",
|
||||
"keyType": "GPGKeys",
|
||||
"keyPath": "@keydir@/personal-pubkey.gpg"
|
||||
}
|
||||
],
|
||||
"localhost:5006/myns/mirroring-mirror": [
|
||||
{
|
||||
"type": "signedBy",
|
||||
"keyType": "GPGKeys",
|
||||
"keyPath": "@keydir@/personal-pubkey.gpg"
|
||||
}
|
||||
],
|
||||
"localhost:5006/myns/mirroring-remap": [
|
||||
{
|
||||
"type": "signedBy",
|
||||
"keyType": "GPGKeys",
|
||||
"keyPath": "@keydir@/personal-pubkey.gpg",
|
||||
"signedIdentity": {
|
||||
"type": "remapIdentity",
|
||||
"prefix": "localhost:5006/myns/mirroring-remap",
|
||||
"signedPrefix": "localhost:5006/myns/mirroring-primary"
|
||||
}
|
||||
}
|
||||
],
|
||||
"docker.io/openshift": [
|
||||
{
|
||||
"type": "insecureAcceptAnything"
|
||||
}
|
||||
],
|
||||
"quay.io/openshift": [
|
||||
{
|
||||
"type": "insecureAcceptAnything"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dir": {
|
||||
|
||||
@@ -26,3 +26,9 @@ mirror = [
|
||||
{ location = "wrong-mirror-0.invalid" },
|
||||
{ location = "gcr.io/google-containers" },
|
||||
]
|
||||
|
||||
[[registry]]
|
||||
location = "localhost:5006/myns/mirroring-primary"
|
||||
mirror = [
|
||||
{ location = "localhost:5006/myns/mirroring-mirror"},
|
||||
]
|
||||
|
||||
@@ -19,8 +19,8 @@ to start a container, then within the container:
|
||||
SKOPEO_CONTAINER_TESTS=1 PS1='nested> ' go test -tags openshift_shell -timeout=24h ./integration -v -check.v -check.vv -check.f='CopySuite.TestRunShell'
|
||||
|
||||
An example of what can be done within the container:
|
||||
cd ..; make binary-local install
|
||||
./skopeo --tls-verify=false copy --sign-by=personal@example.com docker://busybox:latest atomic:localhost:5000/myns/personal:personal
|
||||
cd ..; make bin/skopeo PREFIX=/usr install
|
||||
./skopeo --tls-verify=false copy --sign-by=personal@example.com docker://quay.io/libpod/busybox:latest atomic:localhost:5000/myns/personal:personal
|
||||
oc get istag personal:personal -o json
|
||||
curl -L -v 'http://localhost:5000/v2/'
|
||||
cat ~/.docker/config.json
|
||||
|
||||
@@ -11,8 +11,26 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/go-check/check"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
// A repository with a path with multiple components in it which
|
||||
// contains multiple tags, preferably with some tags pointing to
|
||||
// manifest lists, and with some tags that don't.
|
||||
pullableRepo = "quay.io/libpod/testimage"
|
||||
// A tagged image in the repository that we can inspect and copy.
|
||||
pullableTaggedImage = "registry.k8s.io/coredns/coredns:v1.6.6"
|
||||
// A tagged manifest list in the repository that we can inspect and copy.
|
||||
pullableTaggedManifestList = "registry.k8s.io/coredns/coredns:v1.8.0"
|
||||
// A repository containing multiple tags, some of which are for
|
||||
// manifest lists, and which includes a "latest" tag. We specify the
|
||||
// name here without a tag.
|
||||
pullableRepoWithLatestTag = "registry.k8s.io/pause"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -94,8 +112,8 @@ func (s *SyncSuite) TestDocker2DirTagged(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of neeeding an Internet connection.
|
||||
image := "busybox:latest"
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
|
||||
image := pullableTaggedImage
|
||||
imageRef, err := docker.ParseReference(fmt.Sprintf("//%s", image))
|
||||
c.Assert(err, check.IsNil)
|
||||
imagePath := imageRef.DockerReference().String()
|
||||
@@ -117,9 +135,37 @@ func (s *SyncSuite) TestDocker2DirTagged(c *check.C) {
|
||||
c.Assert(out, check.Equals, "")
|
||||
}
|
||||
|
||||
func (s *SyncSuite) TestDocker2DirTaggedAll(c *check.C) {
|
||||
tmpDir, err := ioutil.TempDir("", "skopeo-sync-test")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
|
||||
image := pullableTaggedManifestList
|
||||
imageRef, err := docker.ParseReference(fmt.Sprintf("//%s", image))
|
||||
c.Assert(err, check.IsNil)
|
||||
imagePath := imageRef.DockerReference().String()
|
||||
|
||||
dir1 := path.Join(tmpDir, "dir1")
|
||||
dir2 := path.Join(tmpDir, "dir2")
|
||||
|
||||
// sync docker => dir
|
||||
assertSkopeoSucceeds(c, "", "sync", "--all", "--scoped", "--src", "docker", "--dest", "dir", image, dir1)
|
||||
_, err = os.Stat(path.Join(dir1, imagePath, "manifest.json"))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// copy docker => dir
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "docker://"+image, "dir:"+dir2)
|
||||
_, err = os.Stat(path.Join(dir2, "manifest.json"))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
out := combinedOutputOfCommand(c, "diff", "-urN", path.Join(dir1, imagePath), dir2)
|
||||
c.Assert(out, check.Equals, "")
|
||||
}
|
||||
|
||||
func (s *SyncSuite) TestScoped(c *check.C) {
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of neeeding an Internet connection.
|
||||
image := "busybox:latest"
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
|
||||
image := pullableTaggedImage
|
||||
imageRef, err := docker.ParseReference(fmt.Sprintf("//%s", image))
|
||||
c.Assert(err, check.IsNil)
|
||||
imagePath := imageRef.DockerReference().String()
|
||||
@@ -127,7 +173,7 @@ func (s *SyncSuite) TestScoped(c *check.C) {
|
||||
dir1, err := ioutil.TempDir("", "skopeo-sync-test")
|
||||
c.Assert(err, check.IsNil)
|
||||
assertSkopeoSucceeds(c, "", "sync", "--src", "docker", "--dest", "dir", image, dir1)
|
||||
_, err = os.Stat(path.Join(dir1, image, "manifest.json"))
|
||||
_, err = os.Stat(path.Join(dir1, path.Base(imagePath), "manifest.json"))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
assertSkopeoSucceeds(c, "", "sync", "--scoped", "--src", "docker", "--dest", "dir", image, dir1)
|
||||
@@ -138,30 +184,30 @@ func (s *SyncSuite) TestScoped(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *SyncSuite) TestDirIsNotOverwritten(c *check.C) {
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of neeeding an Internet connection.
|
||||
image := "busybox:latest"
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
|
||||
image := pullableRepoWithLatestTag
|
||||
imageRef, err := docker.ParseReference(fmt.Sprintf("//%s", image))
|
||||
c.Assert(err, check.IsNil)
|
||||
imagePath := imageRef.DockerReference().String()
|
||||
|
||||
// make a copy of the image in the local registry
|
||||
assertSkopeoSucceeds(c, "", "copy", "--dest-tls-verify=false", "docker://"+image, "docker://"+path.Join(v2DockerRegistryURL, image))
|
||||
assertSkopeoSucceeds(c, "", "copy", "--dest-tls-verify=false", "docker://"+image, "docker://"+path.Join(v2DockerRegistryURL, reference.Path(imageRef.DockerReference())))
|
||||
|
||||
//sync upstream image to dir, not scoped
|
||||
dir1, err := ioutil.TempDir("", "skopeo-sync-test")
|
||||
c.Assert(err, check.IsNil)
|
||||
assertSkopeoSucceeds(c, "", "sync", "--src", "docker", "--dest", "dir", image, dir1)
|
||||
_, err = os.Stat(path.Join(dir1, image, "manifest.json"))
|
||||
_, err = os.Stat(path.Join(dir1, path.Base(imagePath), "manifest.json"))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
//sync local registry image to dir, not scoped
|
||||
assertSkopeoFails(c, ".*Refusing to overwrite destination directory.*", "sync", "--src-tls-verify=false", "--src", "docker", "--dest", "dir", path.Join(v2DockerRegistryURL, image), dir1)
|
||||
assertSkopeoFails(c, ".*Refusing to overwrite destination directory.*", "sync", "--src-tls-verify=false", "--src", "docker", "--dest", "dir", path.Join(v2DockerRegistryURL, reference.Path(imageRef.DockerReference())), dir1)
|
||||
|
||||
//sync local registry image to dir, scoped
|
||||
imageRef, err = docker.ParseReference(fmt.Sprintf("//%s", path.Join(v2DockerRegistryURL, image)))
|
||||
imageRef, err = docker.ParseReference(fmt.Sprintf("//%s", path.Join(v2DockerRegistryURL, reference.Path(imageRef.DockerReference()))))
|
||||
c.Assert(err, check.IsNil)
|
||||
imagePath = imageRef.DockerReference().String()
|
||||
assertSkopeoSucceeds(c, "", "sync", "--scoped", "--src-tls-verify=false", "--src", "docker", "--dest", "dir", path.Join(v2DockerRegistryURL, image), dir1)
|
||||
assertSkopeoSucceeds(c, "", "sync", "--scoped", "--src-tls-verify=false", "--src", "docker", "--dest", "dir", path.Join(v2DockerRegistryURL, reference.Path(imageRef.DockerReference())), dir1)
|
||||
_, err = os.Stat(path.Join(dir1, imagePath, "manifest.json"))
|
||||
c.Assert(err, check.IsNil)
|
||||
os.RemoveAll(dir1)
|
||||
@@ -173,8 +219,8 @@ func (s *SyncSuite) TestDocker2DirUntagged(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of neeeding an Internet connection.
|
||||
image := "alpine"
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
|
||||
image := pullableRepo
|
||||
imageRef, err := docker.ParseReference(fmt.Sprintf("//%s", image))
|
||||
c.Assert(err, check.IsNil)
|
||||
imagePath := imageRef.DockerReference().String()
|
||||
@@ -198,7 +244,7 @@ func (s *SyncSuite) TestYamlUntagged(c *check.C) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
dir1 := path.Join(tmpDir, "dir1")
|
||||
|
||||
image := "alpine"
|
||||
image := pullableRepo
|
||||
imageRef, err := docker.ParseReference(fmt.Sprintf("//%s", image))
|
||||
c.Assert(err, check.IsNil)
|
||||
imagePath := imageRef.DockerReference().Name()
|
||||
@@ -209,23 +255,22 @@ func (s *SyncSuite) TestYamlUntagged(c *check.C) {
|
||||
c.Check(len(tags), check.Not(check.Equals), 0)
|
||||
|
||||
yamlConfig := fmt.Sprintf(`
|
||||
docker.io:
|
||||
%s:
|
||||
images:
|
||||
%s:
|
||||
`, image)
|
||||
%s: []
|
||||
`, reference.Domain(imageRef.DockerReference()), reference.Path(imageRef.DockerReference()))
|
||||
|
||||
//sync to the local reg
|
||||
// sync to the local registry
|
||||
yamlFile := path.Join(tmpDir, "registries.yaml")
|
||||
ioutil.WriteFile(yamlFile, []byte(yamlConfig), 0644)
|
||||
assertSkopeoSucceeds(c, "", "sync", "--scoped", "--src", "yaml", "--dest", "docker", "--dest-tls-verify=false", yamlFile, v2DockerRegistryURL)
|
||||
// sync back from local reg to a folder
|
||||
// sync back from local registry to a folder
|
||||
os.Remove(yamlFile)
|
||||
yamlConfig = fmt.Sprintf(`
|
||||
%s:
|
||||
tls-verify: false
|
||||
images:
|
||||
%s:
|
||||
|
||||
%s: []
|
||||
`, v2DockerRegistryURL, imagePath)
|
||||
|
||||
ioutil.WriteFile(yamlFile, []byte(yamlConfig), 0644)
|
||||
@@ -234,7 +279,9 @@ docker.io:
|
||||
sysCtx = types.SystemContext{
|
||||
DockerInsecureSkipTLSVerify: types.NewOptionalBool(true),
|
||||
}
|
||||
localTags, err := docker.GetRepositoryTags(context.Background(), &sysCtx, imageRef)
|
||||
localImageRef, err := docker.ParseReference(fmt.Sprintf("//%s/%s", v2DockerRegistryURL, imagePath))
|
||||
c.Assert(err, check.IsNil)
|
||||
localTags, err := docker.GetRepositoryTags(context.Background(), &sysCtx, localImageRef)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Check(len(localTags), check.Not(check.Equals), 0)
|
||||
c.Assert(len(localTags), check.Equals, len(tags))
|
||||
@@ -255,6 +302,71 @@ docker.io:
|
||||
c.Assert(nManifests, check.Equals, len(tags))
|
||||
}
|
||||
|
||||
func (s *SyncSuite) TestYamlRegex2Dir(c *check.C) {
|
||||
tmpDir, err := ioutil.TempDir("", "skopeo-sync-test")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
dir1 := path.Join(tmpDir, "dir1")
|
||||
|
||||
yamlConfig := `
|
||||
registry.k8s.io:
|
||||
images-by-tag-regex:
|
||||
pause: ^[12]\.0$ # regex string test
|
||||
`
|
||||
// the ↑ regex strings always matches only 2 images
|
||||
var nTags = 2
|
||||
c.Assert(nTags, check.Not(check.Equals), 0)
|
||||
|
||||
yamlFile := path.Join(tmpDir, "registries.yaml")
|
||||
ioutil.WriteFile(yamlFile, []byte(yamlConfig), 0644)
|
||||
assertSkopeoSucceeds(c, "", "sync", "--scoped", "--src", "yaml", "--dest", "dir", yamlFile, dir1)
|
||||
|
||||
nManifests := 0
|
||||
err = filepath.Walk(dir1, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && info.Name() == "manifest.json" {
|
||||
nManifests++
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(nManifests, check.Equals, nTags)
|
||||
}
|
||||
|
||||
func (s *SyncSuite) TestYamlDigest2Dir(c *check.C) {
|
||||
tmpDir, err := ioutil.TempDir("", "skopeo-sync-test")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
dir1 := path.Join(tmpDir, "dir1")
|
||||
|
||||
yamlConfig := `
|
||||
registry.k8s.io:
|
||||
images:
|
||||
pause:
|
||||
- sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610
|
||||
`
|
||||
yamlFile := path.Join(tmpDir, "registries.yaml")
|
||||
ioutil.WriteFile(yamlFile, []byte(yamlConfig), 0644)
|
||||
assertSkopeoSucceeds(c, "", "sync", "--scoped", "--src", "yaml", "--dest", "dir", yamlFile, dir1)
|
||||
|
||||
nManifests := 0
|
||||
err = filepath.Walk(dir1, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && info.Name() == "manifest.json" {
|
||||
nManifests++
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(nManifests, check.Equals, 1)
|
||||
}
|
||||
|
||||
func (s *SyncSuite) TestYaml2Dir(c *check.C) {
|
||||
tmpDir, err := ioutil.TempDir("", "skopeo-sync-test")
|
||||
c.Assert(err, check.IsNil)
|
||||
@@ -262,22 +374,21 @@ func (s *SyncSuite) TestYaml2Dir(c *check.C) {
|
||||
dir1 := path.Join(tmpDir, "dir1")
|
||||
|
||||
yamlConfig := `
|
||||
docker.io:
|
||||
registry.k8s.io:
|
||||
images:
|
||||
busybox:
|
||||
- latest
|
||||
- musl
|
||||
alpine:
|
||||
- edge
|
||||
- 3.8
|
||||
|
||||
opensuse/leap:
|
||||
coredns/coredns:
|
||||
- v1.8.0
|
||||
- v1.7.1
|
||||
k8s-dns-kube-dns:
|
||||
- 1.14.12
|
||||
- 1.14.13
|
||||
pause:
|
||||
- latest
|
||||
|
||||
quay.io:
|
||||
images:
|
||||
quay/busybox:
|
||||
- latest`
|
||||
quay/busybox:
|
||||
- latest`
|
||||
|
||||
// get the number of tags
|
||||
re := regexp.MustCompile(`^ +- +[^:/ ]+`)
|
||||
@@ -314,10 +425,10 @@ func (s *SyncSuite) TestYamlTLSVerify(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
dir1 := path.Join(tmpDir, "dir1")
|
||||
image := "busybox"
|
||||
image := pullableRepoWithLatestTag
|
||||
tag := "latest"
|
||||
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of neeeding an Internet connection.
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
|
||||
// copy docker => docker
|
||||
assertSkopeoSucceeds(c, "", "copy", "--dest-tls-verify=false", "docker://"+image+":"+tag, localRegURL+image+":"+tag)
|
||||
|
||||
@@ -363,6 +474,26 @@ func (s *SyncSuite) TestYamlTLSVerify(c *check.C) {
|
||||
|
||||
}
|
||||
|
||||
func (s *SyncSuite) TestSyncManifestOutput(c *check.C) {
|
||||
tmpDir, err := ioutil.TempDir("", "sync-manifest-output")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
destDir1 := filepath.Join(tmpDir, "dest1")
|
||||
destDir2 := filepath.Join(tmpDir, "dest2")
|
||||
destDir3 := filepath.Join(tmpDir, "dest3")
|
||||
|
||||
//Split image:tag path from image URI for manifest comparison
|
||||
imageDir := pullableTaggedImage[strings.LastIndex(pullableTaggedImage, "/")+1:]
|
||||
|
||||
assertSkopeoSucceeds(c, "", "sync", "--format=oci", "--all", "--src", "docker", "--dest", "dir", pullableTaggedImage, destDir1)
|
||||
verifyManifestMIMEType(c, filepath.Join(destDir1, imageDir), imgspecv1.MediaTypeImageManifest)
|
||||
assertSkopeoSucceeds(c, "", "sync", "--format=v2s2", "--all", "--src", "docker", "--dest", "dir", pullableTaggedImage, destDir2)
|
||||
verifyManifestMIMEType(c, filepath.Join(destDir2, imageDir), manifest.DockerV2Schema2MediaType)
|
||||
assertSkopeoSucceeds(c, "", "sync", "--format=v2s1", "--all", "--src", "docker", "--dest", "dir", pullableTaggedImage, destDir3)
|
||||
verifyManifestMIMEType(c, filepath.Join(destDir3, imageDir), manifest.DockerV2Schema1SignedMediaType)
|
||||
}
|
||||
|
||||
func (s *SyncSuite) TestDocker2DockerTagged(c *check.C) {
|
||||
const localRegURL = "docker://" + v2DockerRegistryURL + "/"
|
||||
|
||||
@@ -370,8 +501,8 @@ func (s *SyncSuite) TestDocker2DockerTagged(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of neeeding an Internet connection.
|
||||
image := "busybox:latest"
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
|
||||
image := pullableTaggedImage
|
||||
imageRef, err := docker.ParseReference(fmt.Sprintf("//%s", image))
|
||||
c.Assert(err, check.IsNil)
|
||||
imagePath := imageRef.DockerReference().String()
|
||||
@@ -403,8 +534,8 @@ func (s *SyncSuite) TestDir2DockerTagged(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of neeeding an Internet connection.
|
||||
image := "busybox:latest"
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
|
||||
image := pullableRepoWithLatestTag
|
||||
|
||||
dir1 := path.Join(tmpDir, "dir1")
|
||||
err = os.Mkdir(dir1, 0755)
|
||||
@@ -413,6 +544,10 @@ func (s *SyncSuite) TestDir2DockerTagged(c *check.C) {
|
||||
err = os.Mkdir(dir2, 0755)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// create leading dirs
|
||||
err = os.MkdirAll(path.Dir(path.Join(dir1, image)), 0755)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// copy docker => dir
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://"+image, "dir:"+path.Join(dir1, image))
|
||||
_, err = os.Stat(path.Join(dir1, image, "manifest.json"))
|
||||
@@ -421,9 +556,13 @@ func (s *SyncSuite) TestDir2DockerTagged(c *check.C) {
|
||||
// sync dir => docker
|
||||
assertSkopeoSucceeds(c, "", "sync", "--scoped", "--dest-tls-verify=false", "--src", "dir", "--dest", "docker", dir1, v2DockerRegistryURL)
|
||||
|
||||
// create leading dirs
|
||||
err = os.MkdirAll(path.Dir(path.Join(dir2, image)), 0755)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// copy docker => dir
|
||||
assertSkopeoSucceeds(c, "", "copy", "--src-tls-verify=false", localRegURL+image, "dir:"+path.Join(dir2, image))
|
||||
_, err = os.Stat(path.Join(path.Join(dir2, image), "manifest.json"))
|
||||
_, err = os.Stat(path.Join(dir2, image, "manifest.json"))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
out := combinedOutputOfCommand(c, "diff", "-urN", dir1, dir2)
|
||||
@@ -486,7 +625,7 @@ func (s *SyncSuite) TestFailsWithDockerSourceUnauthorized(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *SyncSuite) TestFailsWithDockerSourceNotExisting(c *check.C) {
|
||||
repo := path.Join(v2DockerRegistryURL, "imagedoesdotexist")
|
||||
repo := path.Join(v2DockerRegistryURL, "imagedoesnotexist")
|
||||
tmpDir, err := ioutil.TempDir("", "skopeo-sync-test")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
@@ -10,12 +10,17 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
const skopeoBinary = "skopeo"
|
||||
const decompressDirsBinary = "./decompress-dirs.sh"
|
||||
|
||||
const testFQIN = "docker://quay.io/libpod/busybox" // tag left off on purpose, some tests need to add a special one
|
||||
const testFQIN64 = "docker://quay.io/libpod/busybox:amd64"
|
||||
const testFQINMultiLayer = "docker://quay.io/libpod/alpine_nginx:master" // multi-layer
|
||||
|
||||
// consumeAndLogOutputStream takes (f, err) from an exec.*Pipe(), and causes all output to it to be logged to c.
|
||||
func consumeAndLogOutputStream(c *check.C, id string, f io.ReadCloser, err error) {
|
||||
c.Assert(err, check.IsNil)
|
||||
@@ -200,3 +205,11 @@ func runDecompressDirs(c *check.C, regexp string, args ...string) {
|
||||
c.Assert(string(out), check.Matches, "(?s)"+regexp) // (?s) : '.' will also match newlines
|
||||
}
|
||||
}
|
||||
|
||||
// Verify manifest in a dir: image at dir is expectedMIMEType.
|
||||
func verifyManifestMIMEType(c *check.C, dir string, expectedMIMEType string) {
|
||||
manifestBlob, err := ioutil.ReadFile(filepath.Join(dir, "manifest.json"))
|
||||
c.Assert(err, check.IsNil)
|
||||
mimeType := manifest.GuessMIMEType(manifestBlob)
|
||||
c.Assert(mimeType, check.Equals, expectedMIMEType)
|
||||
}
|
||||
|
||||
67
nix/default-arm64.nix
Normal file
67
nix/default-arm64.nix
Normal file
@@ -0,0 +1,67 @@
|
||||
let
|
||||
pkgs = (import ./nixpkgs.nix {
|
||||
crossSystem = {
|
||||
config = "aarch64-unknown-linux-gnu";
|
||||
};
|
||||
config = {
|
||||
packageOverrides = pkg: {
|
||||
gpgme = (static pkg.gpgme);
|
||||
libassuan = (static pkg.libassuan);
|
||||
libgpgerror = (static pkg.libgpgerror);
|
||||
libseccomp = (static pkg.libseccomp);
|
||||
glib = (static pkg.glib).overrideAttrs (x: {
|
||||
outputs = [ "bin" "out" "dev" ];
|
||||
mesonFlags = [
|
||||
"-Ddefault_library=static"
|
||||
"-Ddevbindir=${placeholder ''dev''}/bin"
|
||||
"-Dgtk_doc=false"
|
||||
"-Dnls=disabled"
|
||||
];
|
||||
postInstall = ''
|
||||
moveToOutput "share/glib-2.0" "$dev"
|
||||
substituteInPlace "$dev/bin/gdbus-codegen" --replace "$out" "$dev"
|
||||
sed -i "$dev/bin/glib-gettextize" -e "s|^gettext_dir=.*|gettext_dir=$dev/share/glib-2.0/gettext|"
|
||||
sed '1i#line 1 "${x.pname}-${x.version}/include/glib-2.0/gobject/gobjectnotifyqueue.c"' \
|
||||
-i "$dev"/include/glib-2.0/gobject/gobjectnotifyqueue.c
|
||||
'';
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
static = pkg: pkg.overrideAttrs (x: {
|
||||
doCheck = false;
|
||||
configureFlags = (x.configureFlags or [ ]) ++ [
|
||||
"--without-shared"
|
||||
"--disable-shared"
|
||||
];
|
||||
dontDisableStatic = true;
|
||||
enableSharedExecutables = false;
|
||||
enableStatic = true;
|
||||
});
|
||||
|
||||
self = with pkgs; buildGoModule rec {
|
||||
name = "skopeo";
|
||||
src = ./..;
|
||||
vendorSha256 = null;
|
||||
doCheck = false;
|
||||
enableParallelBuilding = true;
|
||||
outputs = [ "out" ];
|
||||
nativeBuildInputs = [ bash gitMinimal go-md2man installShellFiles makeWrapper pkg-config which ];
|
||||
buildInputs = [ glibc glibc.static gpgme libassuan libgpgerror libseccomp ];
|
||||
prePatch = ''
|
||||
export CFLAGS='-static -pthread'
|
||||
export LDFLAGS='-s -w -static-libgcc -static'
|
||||
export EXTRA_LDFLAGS='-s -w -linkmode external -extldflags "-static -lm"'
|
||||
export BUILDTAGS='static netgo osusergo exclude_graphdriver_btrfs exclude_graphdriver_devicemapper'
|
||||
'';
|
||||
buildPhase = ''
|
||||
patchShebangs .
|
||||
make bin/skopeo
|
||||
'';
|
||||
installPhase = ''
|
||||
install -Dm755 bin/skopeo $out/bin/skopeo
|
||||
'';
|
||||
};
|
||||
in
|
||||
self
|
||||
65
nix/default.nix
Normal file
65
nix/default.nix
Normal file
@@ -0,0 +1,65 @@
|
||||
{ system ? builtins.currentSystem }:
|
||||
let
|
||||
pkgs = (import ./nixpkgs.nix {
|
||||
config = {
|
||||
packageOverrides = pkg: {
|
||||
gpgme = (static pkg.gpgme);
|
||||
libassuan = (static pkg.libassuan);
|
||||
libgpgerror = (static pkg.libgpgerror);
|
||||
libseccomp = (static pkg.libseccomp);
|
||||
glib = (static pkg.glib).overrideAttrs (x: {
|
||||
outputs = [ "bin" "out" "dev" ];
|
||||
mesonFlags = [
|
||||
"-Ddefault_library=static"
|
||||
"-Ddevbindir=${placeholder ''dev''}/bin"
|
||||
"-Dgtk_doc=false"
|
||||
"-Dnls=disabled"
|
||||
];
|
||||
postInstall = ''
|
||||
moveToOutput "share/glib-2.0" "$dev"
|
||||
substituteInPlace "$dev/bin/gdbus-codegen" --replace "$out" "$dev"
|
||||
sed -i "$dev/bin/glib-gettextize" -e "s|^gettext_dir=.*|gettext_dir=$dev/share/glib-2.0/gettext|"
|
||||
sed '1i#line 1 "${x.pname}-${x.version}/include/glib-2.0/gobject/gobjectnotifyqueue.c"' \
|
||||
-i "$dev"/include/glib-2.0/gobject/gobjectnotifyqueue.c
|
||||
'';
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
static = pkg: pkg.overrideAttrs (x: {
|
||||
doCheck = false;
|
||||
configureFlags = (x.configureFlags or [ ]) ++ [
|
||||
"--without-shared"
|
||||
"--disable-shared"
|
||||
];
|
||||
dontDisableStatic = true;
|
||||
enableSharedExecutables = false;
|
||||
enableStatic = true;
|
||||
});
|
||||
|
||||
self = with pkgs; buildGoModule rec {
|
||||
name = "skopeo";
|
||||
src = ./..;
|
||||
vendorSha256 = null;
|
||||
doCheck = false;
|
||||
enableParallelBuilding = true;
|
||||
outputs = [ "out" ];
|
||||
nativeBuildInputs = [ bash gitMinimal go-md2man installShellFiles makeWrapper pkg-config which ];
|
||||
buildInputs = [ glibc glibc.static gpgme libassuan libgpgerror libseccomp ];
|
||||
prePatch = ''
|
||||
export CFLAGS='-static -pthread'
|
||||
export LDFLAGS='-s -w -static-libgcc -static'
|
||||
export EXTRA_LDFLAGS='-s -w -linkmode external -extldflags "-static -lm"'
|
||||
export BUILDTAGS='static netgo osusergo exclude_graphdriver_btrfs exclude_graphdriver_devicemapper'
|
||||
'';
|
||||
buildPhase = ''
|
||||
patchShebangs .
|
||||
make bin/skopeo
|
||||
'';
|
||||
installPhase = ''
|
||||
install -Dm755 bin/skopeo $out/bin/skopeo
|
||||
'';
|
||||
};
|
||||
in
|
||||
self
|
||||
10
nix/nixpkgs.json
Normal file
10
nix/nixpkgs.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"url": "https://github.com/nixos/nixpkgs",
|
||||
"rev": "eb7e1ef185f6c990cda5f71fdc4fb02e76ab06d5",
|
||||
"date": "2021-05-05T23:16:00+02:00",
|
||||
"path": "/nix/store/a98lkhjlsqh32ic2kkrv5kkik6jy25wh-nixpkgs",
|
||||
"sha256": "1ibz204c41g7baqga2iaj11yz9l75cfdylkiqjnk5igm81ivivxg",
|
||||
"fetchSubmodules": false,
|
||||
"deepClone": false,
|
||||
"leaveDotGit": false
|
||||
}
|
||||
8
nix/nixpkgs.nix
Normal file
8
nix/nixpkgs.nix
Normal file
@@ -0,0 +1,8 @@
|
||||
let
|
||||
json = builtins.fromJSON (builtins.readFile ./nixpkgs.json);
|
||||
nixpkgs = import (builtins.fetchTarball {
|
||||
name = "nixos-unstable";
|
||||
url = "${json.url}/archive/${json.rev}.tar.gz";
|
||||
inherit (json) sha256;
|
||||
});
|
||||
in nixpkgs
|
||||
51
release/Makefile
Normal file
51
release/Makefile
Normal file
@@ -0,0 +1,51 @@
|
||||
## This Makefile is used to publish skopeo container images with Travis CI ##
|
||||
## Environment variables, used in this Makefile are specified in .travis.yml
|
||||
|
||||
export DOCKER_CLI_EXPERIMENTAL=enabled
|
||||
GOARCH ?= $(shell go env GOARCH)
|
||||
|
||||
# Dereference variable $(1), return value if non-empty, otherwise raise an error.
|
||||
err_if_empty = $(if $(strip $($(1))),$(strip $($(1))),$(error Required $(1) variable is undefined or empty))
|
||||
|
||||
# Requires two arguments: Names of the username and the password env. vars.
|
||||
define quay_login
|
||||
@echo "$(call err_if_empty,$(2))" | \
|
||||
docker login quay.io -u "$(call err_if_empty,$(1))" --password-stdin
|
||||
endef
|
||||
|
||||
# Build container image of skopeo upstream based on host architecture
|
||||
build-image/upstream:
|
||||
docker build -t "${UPSTREAM_IMAGE}-${GOARCH}" contrib/skopeoimage/upstream
|
||||
|
||||
# Build container image of skopeo stable based on host architecture
|
||||
build-image/stable:
|
||||
docker build -t "${STABLE_IMAGE}-${GOARCH}" contrib/skopeoimage/stable
|
||||
|
||||
# Push container image of skopeo upstream (based on host architecture) to image repository
|
||||
push-image/upstream:
|
||||
$(call quay_login,SKOPEO_QUAY_USERNAME,SKOPEO_QUAY_PASSWORD)
|
||||
docker push "${UPSTREAM_IMAGE}-${GOARCH}"
|
||||
|
||||
# Push container image of skopeo stable (based on host architecture) to image default and extra repositories
|
||||
push-image/stable:
|
||||
$(call quay_login,SKOPEO_QUAY_USERNAME,SKOPEO_QUAY_PASSWORD)
|
||||
docker push "${STABLE_IMAGE}-${GOARCH}"
|
||||
docker tag "${STABLE_IMAGE}-${GOARCH}" "${EXTRA_STABLE_IMAGE}-${GOARCH}"
|
||||
$(call quay_login,CONTAINERS_QUAY_USERNAME,CONTAINERS_QUAY_PASSWORD)
|
||||
docker push "${EXTRA_STABLE_IMAGE}-${GOARCH}"
|
||||
|
||||
# Create and push multiarch image manifest of skopeo upstream
|
||||
push-manifest-multiarch/upstream:
|
||||
docker manifest create "${UPSTREAM_IMAGE}" $(foreach arch,${MULTIARCH_MANIFEST_ARCHITECTURES}, ${UPSTREAM_IMAGE}-${arch})
|
||||
$(call quay_login,SKOPEO_QUAY_USERNAME,SKOPEO_QUAY_PASSWORD)
|
||||
docker manifest push --purge "${UPSTREAM_IMAGE}"
|
||||
|
||||
# Create and push multiarch image manifest of skopeo stable
|
||||
push-manifest-multiarch/stable:
|
||||
docker manifest create "${STABLE_IMAGE}" $(foreach arch,${MULTIARCH_MANIFEST_ARCHITECTURES}, ${STABLE_IMAGE}-${arch})
|
||||
$(call quay_login,SKOPEO_QUAY_USERNAME,SKOPEO_QUAY_PASSWORD)
|
||||
docker manifest push --purge "${STABLE_IMAGE}"
|
||||
# Push to extra repository
|
||||
docker manifest create "${EXTRA_STABLE_IMAGE}" $(foreach arch,${MULTIARCH_MANIFEST_ARCHITECTURES}, ${EXTRA_STABLE_IMAGE}-${arch})
|
||||
$(call quay_login,CONTAINERS_QUAY_USERNAME,CONTAINERS_QUAY_PASSWORD)
|
||||
docker manifest push --purge "${EXTRA_STABLE_IMAGE}"
|
||||
40
release/README.md
Normal file
40
release/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# skopeo container image build with Travis
|
||||
|
||||
This document describes the details and requirements to build and publish skopeo container images. The images are published as several architecture specific images and multiarch images on top for upstream and stable versions.
|
||||
|
||||
The Travis configuration is available at `.travis.yml`.
|
||||
|
||||
The code to build and publish images is available at `release/Makefile` and should be used only via Travis.
|
||||
|
||||
Travis workflow has 3 major pieces:
|
||||
- `local-build` - build and test source code locally on osx and linux/amd64 environments, 2 jobs are running in parallel
|
||||
- `image-build-push` - build and push container images with several Travis jobs running in parallel to build images for several architectures (linux/amd64, linux/s390x, linux/ppc64le). Build part is done for each PR, push part is executed only in case of cron job or master branch update.
|
||||
- `manifest-multiarch-push` - create and push image manifests, which consists of architecture specific images from previous step. Executed only in case of cron job or master branch update.
|
||||
|
||||
## Ways to have full workflow run
|
||||
- [cron job](https://docs.travis-ci.com/user/cron-jobs/#adding-cron-jobs)
|
||||
- Trigger build from Travis CI
|
||||
- Update code in master branch
|
||||
|
||||
## Environment variables
|
||||
|
||||
Several environment variables are used to customize image names and keep private credentials to push to quay.io repositories.
|
||||
|
||||
**Image tags** are specified in environment variable and should be manually updated in case of new release.
|
||||
|
||||
- `SKOPEO_QUAY_USERNAME` and `SKOPEO_QUAY_PASSWORD` are credentials to push images to `quay.io/skopeo/stable` and `quay.io/skopeo/upstream` repos, and require the credentials to have write permissions. These variables should be specified in [Travis](https://docs.travis-ci.com/user/environment-variables/#defining-variables-in-repository-settings).
|
||||
- `CONTAINERS_QUAY_USERNAME` and `CONTAINERS_QUAY_PASSWORD` are credentials to push images to `quay.io/containers/skopeo` repos, and require the credentials to have write permissions. These variables should be specified in [Travis](https://docs.travis-ci.com/user/environment-variables/#defining-variables-in-repository-settings).
|
||||
|
||||
Variables in .travis.yml
|
||||
- `MULTIARCH_MANIFEST_ARCHITECTURES` is a list with architecture shortnames, to appear in final multiarch manifest. The values should fit to architectures used in the `image-build-push` Travis step.
|
||||
- `STABLE_IMAGE`, `EXTRA_STABLE_IMAGE` are image names to publish stable Skopeo.
|
||||
- `UPSTREAM_IMAGE` is an image name to publish upstream Skopeo.
|
||||
|
||||
### Values for environment variables
|
||||
|
||||
| Env variable | Value |
|
||||
| -------------------------------- |----------------------------------|
|
||||
| MULTIARCH_MANIFEST_ARCHITECTURES | "amd64 s390x ppc64le" |
|
||||
| STABLE_IMAGE | quay.io/skopeo/stable:v1.2.0 |
|
||||
| EXTRA_STABLE_IMAGE | quay.io/containers/skopeo:v1.2.0 |
|
||||
| UPSTREAM_IMAGE | quay.io/skopeo/upstream:master |
|
||||
@@ -27,11 +27,20 @@ load helpers
|
||||
# Now run inspect locally
|
||||
run_skopeo inspect dir:$workdir
|
||||
inspect_local=$output
|
||||
run_skopeo inspect --raw dir:$workdir
|
||||
inspect_local_raw=$output
|
||||
config_digest=$(jq -r '.config.digest' <<<"$inspect_local_raw")
|
||||
|
||||
# Each SHA-named file must be listed in the output of 'inspect'
|
||||
# Each SHA-named layer file (but not the config) must be listed in the output of 'inspect'.
|
||||
# In all existing versions of Skopeo (with 1.6 being the current as of this comment),
|
||||
# the output of 'inspect' lists layer digests,
|
||||
# but not the digest of the config blob ($config_digest), if any.
|
||||
layers=$(jq -r '.Layers' <<<"$inspect_local")
|
||||
for sha in $(find $workdir -type f | xargs -l1 basename | egrep '^[0-9a-f]{64}$'); do
|
||||
expect_output --from="$inspect_local" --substring "sha256:$sha" \
|
||||
"Locally-extracted SHA file is present in 'inspect'"
|
||||
if [ "sha256:$sha" != "$config_digest" ]; then
|
||||
expect_output --from="$layers" --substring "sha256:$sha" \
|
||||
"Locally-extracted SHA file is present in 'inspect'"
|
||||
fi
|
||||
done
|
||||
|
||||
# Simple sanity check on 'inspect' output.
|
||||
@@ -65,59 +74,47 @@ END_EXPECT
|
||||
}
|
||||
|
||||
@test "inspect: env" {
|
||||
remote_image=docker://docker.io/fedora:latest
|
||||
remote_image=docker://quay.io/libpod/fedora:31
|
||||
run_skopeo inspect $remote_image
|
||||
inspect_remote=$output
|
||||
|
||||
# Simple check on 'inspect' output with environment variables.
|
||||
# 1) Get remote image values of environment variables (the value of 'Env')
|
||||
# 2) Confirm substring in check_array and the value of 'Env' match.
|
||||
check_array=(PATH=.* )
|
||||
check_array=(FGC=f31 DISTTAG=f31container)
|
||||
remote=$(jq '.Env[]' <<<"$inspect_remote")
|
||||
for substr in ${check_array[@]}; do
|
||||
expect_output --from="$remote" --substring "$substr"
|
||||
done
|
||||
}
|
||||
|
||||
# Tests https://github.com/containers/skopeo/pull/708
|
||||
@test "inspect: image manifest list w/ diff platform" {
|
||||
# When --raw is provided, can inspect show the raw manifest list, w/o
|
||||
# requiring any particular platform to be present
|
||||
# To test whether container image can be inspected successfully w/o
|
||||
# platform dependency.
|
||||
# 1) Get current platform arch
|
||||
# 2) Inspect container image is different from current platform arch
|
||||
# 3) Compare output w/ expected result
|
||||
# This image's manifest is for an os + arch that is... um, unlikely
|
||||
# to support skopeo in the foreseeable future. Or past. The image
|
||||
# is created by the make-noarch-manifest script in this directory.
|
||||
img=docker://quay.io/libpod/notmyarch:20210121
|
||||
|
||||
# Here we see a revolting workaround for a podman incompatibility
|
||||
# change: in April 2020, podman info completely changed format
|
||||
# of the keys. What worked until then now throws an error. We
|
||||
# need to work with both old and new podman.
|
||||
arch=$(podman info --format '{{.host.arch}}' || true)
|
||||
if [[ -z "$arch" ]]; then
|
||||
arch=$(podman info --format '{{.Host.Arch}}')
|
||||
fi
|
||||
# Get our host arch (what we're running on). This assumes that skopeo
|
||||
# arch matches podman; it also assumes running podman >= April 2020
|
||||
# (prior to that, the format keys were lower-case).
|
||||
arch=$(podman info --format '{{.Host.Arch}}')
|
||||
|
||||
case $arch in
|
||||
"amd64")
|
||||
diff_arch_list="s390x ppc64le"
|
||||
;;
|
||||
"s390x")
|
||||
diff_arch_list="amd64 ppc64le"
|
||||
;;
|
||||
"ppc64le")
|
||||
diff_arch_list="amd64 s390x"
|
||||
;;
|
||||
"*")
|
||||
diff_arch_list="amd64 s390x ppc64le"
|
||||
;;
|
||||
esac
|
||||
# By default, 'inspect' tries to match our host os+arch. This should fail.
|
||||
run_skopeo 1 inspect $img
|
||||
expect_output --substring "Error parsing manifest for image: Error choosing image instance: no image found in manifest list for architecture $arch, variant " \
|
||||
"skopeo inspect, without --raw, fails"
|
||||
|
||||
for arch in $diff_arch_list; do
|
||||
remote_image=docker://docker.io/$arch/golang
|
||||
run_skopeo inspect --tls-verify=false --raw $remote_image
|
||||
remote_arch=$(jq -r '.manifests[0]["platform"]["architecture"]' <<< "$output")
|
||||
expect_output --from="$remote_arch" "$arch" "platform arch of $remote_image"
|
||||
done
|
||||
# With --raw, we can inspect
|
||||
run_skopeo inspect --raw $img
|
||||
expect_output --substring "manifests.*platform.*architecture" \
|
||||
"skopeo inspect --raw returns reasonable output"
|
||||
|
||||
# ...and what we get should be consistent with what our script created.
|
||||
archinfo=$(jq -r '.manifests[0].platform | {os,variant,architecture} | join("-")' <<<"$output")
|
||||
|
||||
expect_output --from="$archinfo" "amigaos-1000-mc68000" \
|
||||
"os - variant - architecture of $img"
|
||||
}
|
||||
|
||||
# vim: filetype=sh
|
||||
|
||||
@@ -14,7 +14,7 @@ function setup() {
|
||||
# From remote, to dir1, to local, to dir2;
|
||||
# compare dir1 and dir2, expect no changes
|
||||
@test "copy: dir, round trip" {
|
||||
local remote_image=docker://docker.io/library/busybox:latest
|
||||
local remote_image=docker://quay.io/libpod/busybox:latest
|
||||
local localimg=docker://localhost:5000/busybox:unsigned
|
||||
|
||||
local dir1=$TESTDIR/dir1
|
||||
@@ -30,7 +30,7 @@ function setup() {
|
||||
|
||||
# Same as above, but using 'oci:' instead of 'dir:' and with a :latest tag
|
||||
@test "copy: oci, round trip" {
|
||||
local remote_image=docker://docker.io/library/busybox:latest
|
||||
local remote_image=docker://quay.io/libpod/busybox:latest
|
||||
local localimg=docker://localhost:5000/busybox:unsigned
|
||||
|
||||
local dir1=$TESTDIR/oci1
|
||||
@@ -45,8 +45,8 @@ function setup() {
|
||||
}
|
||||
|
||||
# Compression zstd
|
||||
@test "copy: oci, round trip, zstd" {
|
||||
local remote_image=docker://docker.io/library/busybox:latest
|
||||
@test "copy: oci, zstd" {
|
||||
local remote_image=docker://quay.io/libpod/busybox:latest
|
||||
|
||||
local dir=$TESTDIR/dir
|
||||
|
||||
@@ -57,11 +57,17 @@ function setup() {
|
||||
|
||||
# Check there is at least one file that has the zstd magic number as the first 4 bytes
|
||||
(for i in $dir/blobs/sha256/*; do test "$(head -c 4 $i)" = $magic && exit 0; done; exit 1)
|
||||
|
||||
# Check that the manifest's description of the image's first layer is the zstd layer type
|
||||
instance=$(jq -r '.manifests[0].digest' $dir/index.json)
|
||||
[[ "$instance" != null ]]
|
||||
mediatype=$(jq -r '.layers[0].mediaType' < $dir/blobs/${instance/://})
|
||||
[[ "$mediatype" == "application/vnd.oci.image.layer.v1.tar+zstd" ]]
|
||||
}
|
||||
|
||||
# Same image, extracted once with :tag and once without
|
||||
@test "copy: oci w/ and w/o tags" {
|
||||
local remote_image=docker://docker.io/library/busybox:latest
|
||||
local remote_image=docker://quay.io/libpod/busybox:latest
|
||||
|
||||
local dir1=$TESTDIR/dir1
|
||||
local dir2=$TESTDIR/dir2
|
||||
@@ -78,7 +84,7 @@ function setup() {
|
||||
|
||||
# Registry -> storage -> oci-archive
|
||||
@test "copy: registry -> storage -> oci-archive" {
|
||||
local alpine=docker.io/library/alpine:latest
|
||||
local alpine=quay.io/libpod/alpine:latest
|
||||
local tmp=$TESTDIR/oci
|
||||
|
||||
run_skopeo copy docker://$alpine containers-storage:$alpine
|
||||
@@ -94,6 +100,50 @@ function setup() {
|
||||
docker://localhost:5000/foo
|
||||
}
|
||||
|
||||
# manifest format
|
||||
@test "copy: manifest format" {
|
||||
local remote_image=docker://quay.io/libpod/busybox:latest
|
||||
|
||||
local dir1=$TESTDIR/dir1
|
||||
local dir2=$TESTDIR/dir2
|
||||
|
||||
run_skopeo copy --format v2s2 $remote_image dir:$dir1
|
||||
run_skopeo copy --format oci $remote_image dir:$dir2
|
||||
grep 'application/vnd.docker.distribution.manifest.v2' $dir1/manifest.json
|
||||
grep 'application/vnd.oci.image' $dir2/manifest.json
|
||||
}
|
||||
|
||||
# additional tag
|
||||
@test "copy: additional tag" {
|
||||
local remote_image=docker://quay.io/libpod/busybox:latest
|
||||
|
||||
# additional-tag is supported only for docker-archive
|
||||
run_skopeo copy --additional-tag busybox:mine $remote_image \
|
||||
docker-archive:$TESTDIR/mybusybox.tar:busybox:latest
|
||||
mkdir -p $TESTDIR/podmanroot
|
||||
run podman --root $TESTDIR/podmanroot load -i $TESTDIR/mybusybox.tar
|
||||
run podman --root $TESTDIR/podmanroot images
|
||||
expect_output --substring "mine"
|
||||
|
||||
}
|
||||
|
||||
# shared blob directory
|
||||
@test "copy: shared blob directory" {
|
||||
local remote_image=docker://quay.io/libpod/busybox:latest
|
||||
|
||||
local shareddir=$TESTDIR/shareddir
|
||||
local dir1=$TESTDIR/dir1
|
||||
local dir2=$TESTDIR/dir2
|
||||
|
||||
run_skopeo copy --dest-shared-blob-dir $shareddir \
|
||||
$remote_image oci:$dir1
|
||||
[ -n "$(ls $shareddir)" ]
|
||||
[ -z "$(ls $dir1/blobs)" ]
|
||||
run_skopeo copy --src-shared-blob-dir $shareddir \
|
||||
oci:$dir1 oci:$dir2
|
||||
diff -urN $shareddir $dir2/blobs
|
||||
}
|
||||
|
||||
teardown() {
|
||||
podman rm -f reg
|
||||
|
||||
|
||||
@@ -8,19 +8,28 @@ load helpers
|
||||
function setup() {
|
||||
standard_setup
|
||||
|
||||
start_registry --with-cert reg
|
||||
start_registry --with-cert --enable-delete=true reg
|
||||
}
|
||||
|
||||
@test "local registry, with cert" {
|
||||
# Push to local registry...
|
||||
run_skopeo copy --dest-cert-dir=$TESTDIR/client-auth \
|
||||
docker://docker.io/library/busybox:latest \
|
||||
docker://quay.io/libpod/busybox:latest \
|
||||
docker://localhost:5000/busybox:unsigned
|
||||
|
||||
# ...and pull it back out
|
||||
run_skopeo copy --src-cert-dir=$TESTDIR/client-auth \
|
||||
docker://localhost:5000/busybox:unsigned \
|
||||
dir:$TESTDIR/extracted
|
||||
|
||||
# inspect with cert
|
||||
run_skopeo inspect --cert-dir=$TESTDIR/client-auth \
|
||||
docker://localhost:5000/busybox:unsigned
|
||||
expect_output --substring "localhost:5000/busybox"
|
||||
|
||||
# delete with cert
|
||||
run_skopeo delete --cert-dir=$TESTDIR/client-auth \
|
||||
docker://localhost:5000/busybox:unsigned
|
||||
}
|
||||
|
||||
teardown() {
|
||||
|
||||
@@ -18,7 +18,7 @@ function setup() {
|
||||
testuser=testuser
|
||||
testpassword=$(random_string 15)
|
||||
|
||||
start_registry --testuser=$testuser --testpassword=$testpassword reg
|
||||
start_registry --testuser=$testuser --testpassword=$testpassword --enable-delete=true reg
|
||||
}
|
||||
|
||||
@test "auth: credentials on command line" {
|
||||
@@ -43,7 +43,7 @@ function setup() {
|
||||
|
||||
# These should pass
|
||||
run_skopeo copy --dest-tls-verify=false --dcreds=$testuser:$testpassword \
|
||||
docker://docker.io/library/busybox:latest \
|
||||
docker://quay.io/libpod/busybox:latest \
|
||||
docker://localhost:5000/busybox:mine
|
||||
run_skopeo inspect --tls-verify=false --creds=$testuser:$testpassword \
|
||||
docker://localhost:5000/busybox:mine
|
||||
@@ -55,7 +55,7 @@ function setup() {
|
||||
podman login --tls-verify=false -u $testuser -p $testpassword localhost:5000
|
||||
|
||||
run_skopeo copy --dest-tls-verify=false \
|
||||
docker://docker.io/library/busybox:latest \
|
||||
docker://quay.io/libpod/busybox:latest \
|
||||
docker://localhost:5000/busybox:mine
|
||||
run_skopeo inspect --tls-verify=false docker://localhost:5000/busybox:mine
|
||||
expect_output --substring "localhost:5000/busybox"
|
||||
@@ -67,6 +67,47 @@ function setup() {
|
||||
expect_output --substring "unauthorized: authentication required"
|
||||
}
|
||||
|
||||
@test "auth: copy with --src-creds and --dest-creds" {
|
||||
run_skopeo copy --dest-tls-verify=false --dest-creds=$testuser:$testpassword \
|
||||
docker://quay.io/libpod/busybox:latest \
|
||||
docker://localhost:5000/busybox:mine
|
||||
run_skopeo copy --src-tls-verify=false --src-creds=$testuser:$testpassword \
|
||||
docker://localhost:5000/busybox:mine \
|
||||
dir:$TESTDIR/dir1
|
||||
run ls $TESTDIR/dir1
|
||||
expect_output --substring "manifest.json"
|
||||
}
|
||||
|
||||
@test "auth: credentials via authfile" {
|
||||
podman login --tls-verify=false --authfile $TESTDIR/test.auth -u $testuser -p $testpassword localhost:5000
|
||||
|
||||
# copy without authfile: should fail
|
||||
run_skopeo 1 copy --dest-tls-verify=false \
|
||||
docker://quay.io/libpod/busybox:latest \
|
||||
docker://localhost:5000/busybox:mine
|
||||
|
||||
# copy with authfile: should work
|
||||
run_skopeo copy --dest-tls-verify=false \
|
||||
--authfile $TESTDIR/test.auth \
|
||||
docker://quay.io/libpod/busybox:latest \
|
||||
docker://localhost:5000/busybox:mine
|
||||
|
||||
# inspect without authfile: should fail
|
||||
run_skopeo 1 inspect --tls-verify=false docker://localhost:5000/busybox:mine
|
||||
expect_output --substring "unauthorized: authentication required"
|
||||
|
||||
# inspect with authfile: should work
|
||||
run_skopeo inspect --tls-verify=false --authfile $TESTDIR/test.auth docker://localhost:5000/busybox:mine
|
||||
expect_output --substring "localhost:5000/busybox"
|
||||
|
||||
# delete without authfile: should fail
|
||||
run_skopeo 1 delete --tls-verify=false docker://localhost:5000/busybox:mine
|
||||
expect_output --substring "authentication required"
|
||||
|
||||
# delete with authfile: should work
|
||||
run_skopeo delete --tls-verify=false --authfile $TESTDIR/test.auth docker://localhost:5000/busybox:mine
|
||||
}
|
||||
|
||||
teardown() {
|
||||
podman rm -f reg
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ END_POLICY_JSON
|
||||
fi
|
||||
|
||||
# Cache local copy
|
||||
run_skopeo copy docker://docker.io/library/busybox:latest \
|
||||
run_skopeo copy docker://quay.io/libpod/busybox:latest \
|
||||
dir:$TESTDIR/busybox
|
||||
|
||||
# Push a bunch of images. Do so *without* --policy flag; this lets us
|
||||
@@ -143,6 +143,75 @@ END_PUSH
|
||||
END_TESTS
|
||||
}
|
||||
|
||||
@test "signing: remove signature" {
|
||||
run_skopeo '?' standalone-sign /dev/null busybox alice@test.redhat.com -o /dev/null
|
||||
if [[ "$output" =~ 'signing is not supported' ]]; then
|
||||
skip "skopeo built without support for creating signatures"
|
||||
return 1
|
||||
fi
|
||||
if [ "$status" -ne 0 ]; then
|
||||
die "exit code is $status; expected 0"
|
||||
fi
|
||||
|
||||
# Cache local copy
|
||||
run_skopeo copy docker://quay.io/libpod/busybox:latest \
|
||||
dir:$TESTDIR/busybox
|
||||
# Push a signed image
|
||||
run_skopeo --registries.d $REGISTRIES_D \
|
||||
copy --dest-tls-verify=false \
|
||||
--sign-by=alice@test.redhat.com \
|
||||
dir:$TESTDIR/busybox \
|
||||
docker://localhost:5000/myns/alice:signed
|
||||
# Fetch the image with signature
|
||||
run_skopeo --registries.d $REGISTRIES_D \
|
||||
--policy $POLICY_JSON \
|
||||
copy --src-tls-verify=false \
|
||||
docker://localhost:5000/myns/alice:signed \
|
||||
dir:$TESTDIR/busybox-signed
|
||||
# Fetch the image with removing signature
|
||||
run_skopeo --registries.d $REGISTRIES_D \
|
||||
--policy $POLICY_JSON \
|
||||
copy --src-tls-verify=false \
|
||||
--remove-signatures \
|
||||
docker://localhost:5000/myns/alice:signed \
|
||||
dir:$TESTDIR/busybox-unsigned
|
||||
ls $TESTDIR/busybox-signed | grep "signature"
|
||||
[ -z "$(ls $TESTDIR/busybox-unsigned | grep "signature")" ]
|
||||
}
|
||||
|
||||
@test "signing: standalone" {
|
||||
run_skopeo '?' standalone-sign /dev/null busybox alice@test.redhat.com -o /dev/null
|
||||
if [[ "$output" =~ 'signing is not supported' ]]; then
|
||||
skip "skopeo built without support for creating signatures"
|
||||
return 1
|
||||
fi
|
||||
if [ "$status" -ne 0 ]; then
|
||||
die "exit code is $status; expected 0"
|
||||
fi
|
||||
|
||||
run_skopeo copy --dest-tls-verify=false \
|
||||
docker://quay.io/libpod/busybox:latest \
|
||||
docker://localhost:5000/busybox:latest
|
||||
run_skopeo copy --src-tls-verify=false \
|
||||
docker://localhost:5000/busybox:latest \
|
||||
dir:$TESTDIR/busybox
|
||||
# Standalone sign
|
||||
run_skopeo standalone-sign -o $TESTDIR/busybox.signature \
|
||||
$TESTDIR/busybox/manifest.json \
|
||||
localhost:5000/busybox:latest \
|
||||
alice@test.redhat.com
|
||||
# Standalone verify
|
||||
fingerprint=$(gpg --list-keys | grep -B1 alice.test.redhat.com | head -n 1)
|
||||
run_skopeo standalone-verify $TESTDIR/busybox/manifest.json \
|
||||
localhost:5000/busybox:latest \
|
||||
$fingerprint \
|
||||
$TESTDIR/busybox.signature
|
||||
# manifest digest
|
||||
digest=$(echo "$output" | awk '{print $4;}')
|
||||
run_skopeo manifest-digest $TESTDIR/busybox/manifest.json
|
||||
expect_output $digest
|
||||
}
|
||||
|
||||
teardown() {
|
||||
podman rm -f reg
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ function setup() {
|
||||
|
||||
# delete image from registry
|
||||
@test "delete: remove image from registry" {
|
||||
local remote_image=docker://docker.io/library/busybox:latest
|
||||
local remote_image=docker://quay.io/libpod/busybox:latest
|
||||
local localimg=docker://localhost:5000/busybox:unsigned
|
||||
local output=
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ SKOPEO_BINARY=${SKOPEO_BINARY:-$(dirname ${BASH_SOURCE})/../skopeo}
|
||||
SKOPEO_TIMEOUT=${SKOPEO_TIMEOUT:-300}
|
||||
|
||||
# Default image to run as a local registry
|
||||
REGISTRY_FQIN=${SKOPEO_TEST_REGISTRY_FQIN:-docker.io/library/registry:2}
|
||||
REGISTRY_FQIN=${SKOPEO_TEST_REGISTRY_FQIN:-quay.io/libpod/registry:2}
|
||||
|
||||
###############################################################################
|
||||
# BEGIN setup/teardown
|
||||
@@ -194,7 +194,7 @@ function expect_output() {
|
||||
fi
|
||||
|
||||
# This is a multi-line message, which may in turn contain multi-line
|
||||
# output, so let's format it ourself, readably
|
||||
# output, so let's format it ourselves, readably
|
||||
local -a actual_split
|
||||
readarray -t actual_split <<<"$actual"
|
||||
printf "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" >&2
|
||||
@@ -314,8 +314,7 @@ start_registry() {
|
||||
fi
|
||||
|
||||
if ! egrep -q "^$testuser:" $AUTHDIR/htpasswd; then
|
||||
log_and_run $PODMAN run --rm --entrypoint htpasswd $REGISTRY_FQIN \
|
||||
-Bbn $testuser $testpassword >> $AUTHDIR/htpasswd
|
||||
htpasswd -Bbn $testuser $testpassword >> $AUTHDIR/htpasswd
|
||||
fi
|
||||
|
||||
reg_args+=(
|
||||
@@ -332,7 +331,8 @@ start_registry() {
|
||||
log_and_run openssl req -newkey rsa:4096 -nodes -sha256 \
|
||||
-keyout $AUTHDIR/domain.key -x509 -days 2 \
|
||||
-out $CERT \
|
||||
-subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=localhost"
|
||||
-subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=registry host certificate" \
|
||||
-addext subjectAltName=DNS:localhost
|
||||
fi
|
||||
|
||||
reg_args+=(
|
||||
@@ -356,7 +356,7 @@ start_registry() {
|
||||
return
|
||||
fi
|
||||
|
||||
timeout=$(expr $timeout - 1)
|
||||
timeout=$(( timeout - 1 ))
|
||||
sleep 1
|
||||
done
|
||||
die "Timed out waiting for registry container to respond on :$port"
|
||||
|
||||
70
systemtest/make-noarch-manifest
Executable file
70
systemtest/make-noarch-manifest
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Tool for creating an image whose OS and arch will (probably) never
|
||||
# match a system on which skopeo will run. This image will be used
|
||||
# in the 'inspect' test.
|
||||
#
|
||||
set -ex
|
||||
|
||||
# Name and tag of the image we create
|
||||
imgname=notmyarch
|
||||
imgtag=$(date +%Y%m%d)
|
||||
|
||||
# (In case older image exists from a prior run)
|
||||
buildah rmi $imgname:$imgtag &>/dev/null || true
|
||||
|
||||
#
|
||||
# Step 1: create an image containing only a README and a copy of this script
|
||||
#
|
||||
id=$(buildah from scratch)
|
||||
|
||||
now=$(date --rfc-3339=seconds)
|
||||
readme=$(mktemp -t README.XXXXXXXX)
|
||||
ME=$(basename $0)
|
||||
|
||||
cat >| $readme <<EOF
|
||||
This is a dummy image intended solely for skopeo testing.
|
||||
|
||||
This image was created $now
|
||||
|
||||
The script used to create this image is available as $ME
|
||||
EOF
|
||||
|
||||
buildah copy $id $readme /README
|
||||
buildah copy $id $0 /$ME
|
||||
|
||||
buildah commit $id my_tmp_image
|
||||
buildah rm $id
|
||||
|
||||
#
|
||||
# Step 2: create a manifest list, then add the above image but with
|
||||
# an os+arch override.
|
||||
#
|
||||
buildah manifest create $imgname:$imgtag
|
||||
|
||||
buildah manifest add \
|
||||
--os amigaos \
|
||||
--arch mc68000 \
|
||||
--variant 1000 \
|
||||
$imgname:$imgtag my_tmp_image
|
||||
|
||||
# Done. Show instructions.
|
||||
cat <<EOF
|
||||
DONE!
|
||||
|
||||
You can inspect the created image with:
|
||||
|
||||
skopeo inspect --raw containers-storage:localhost/$imgname:$imgtag | jq .
|
||||
|
||||
(FIXME: is there a way to, like, mount the image and verify the files?)
|
||||
|
||||
If you're happy with this image, you can now:
|
||||
|
||||
buildah manifest push --all $imgname:$imgtag docker://quay.io/libpod/$imgname:$imgtag
|
||||
|
||||
Once done, you urgently need to:
|
||||
|
||||
buildah rmi $imgname:$imgtag my_tmp_image
|
||||
|
||||
If you don't do this, 'podman images' will barf catastrophically!
|
||||
EOF
|
||||
1
vendor/github.com/Microsoft/go-winio/CODEOWNERS
generated
vendored
Normal file
1
vendor/github.com/Microsoft/go-winio/CODEOWNERS
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @microsoft/containerplat
|
||||
2
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
2
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
@@ -1,4 +1,4 @@
|
||||
# go-winio
|
||||
# go-winio [](https://github.com/microsoft/go-winio/actions/workflows/ci.yml)
|
||||
|
||||
This repository contains utilities for efficiently performing Win32 IO operations in
|
||||
Go. Currently, this is focused on accessing named pipes and other file handles, and
|
||||
|
||||
344
vendor/github.com/Microsoft/go-winio/archive/tar/common.go
generated
vendored
344
vendor/github.com/Microsoft/go-winio/archive/tar/common.go
generated
vendored
@@ -1,344 +0,0 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package tar implements access to tar archives.
|
||||
// It aims to cover most of the variations, including those produced
|
||||
// by GNU and BSD tars.
|
||||
//
|
||||
// References:
|
||||
// http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5
|
||||
// http://www.gnu.org/software/tar/manual/html_node/Standard.html
|
||||
// http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
|
||||
package tar
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
blockSize = 512
|
||||
|
||||
// Types
|
||||
TypeReg = '0' // regular file
|
||||
TypeRegA = '\x00' // regular file
|
||||
TypeLink = '1' // hard link
|
||||
TypeSymlink = '2' // symbolic link
|
||||
TypeChar = '3' // character device node
|
||||
TypeBlock = '4' // block device node
|
||||
TypeDir = '5' // directory
|
||||
TypeFifo = '6' // fifo node
|
||||
TypeCont = '7' // reserved
|
||||
TypeXHeader = 'x' // extended header
|
||||
TypeXGlobalHeader = 'g' // global extended header
|
||||
TypeGNULongName = 'L' // Next file has a long name
|
||||
TypeGNULongLink = 'K' // Next file symlinks to a file w/ a long name
|
||||
TypeGNUSparse = 'S' // sparse file
|
||||
)
|
||||
|
||||
// A Header represents a single header in a tar archive.
|
||||
// Some fields may not be populated.
|
||||
type Header struct {
|
||||
Name string // name of header file entry
|
||||
Mode int64 // permission and mode bits
|
||||
Uid int // user id of owner
|
||||
Gid int // group id of owner
|
||||
Size int64 // length in bytes
|
||||
ModTime time.Time // modified time
|
||||
Typeflag byte // type of header entry
|
||||
Linkname string // target name of link
|
||||
Uname string // user name of owner
|
||||
Gname string // group name of owner
|
||||
Devmajor int64 // major number of character or block device
|
||||
Devminor int64 // minor number of character or block device
|
||||
AccessTime time.Time // access time
|
||||
ChangeTime time.Time // status change time
|
||||
CreationTime time.Time // creation time
|
||||
Xattrs map[string]string
|
||||
Winheaders map[string]string
|
||||
}
|
||||
|
||||
// File name constants from the tar spec.
|
||||
const (
|
||||
fileNameSize = 100 // Maximum number of bytes in a standard tar name.
|
||||
fileNamePrefixSize = 155 // Maximum number of ustar extension bytes.
|
||||
)
|
||||
|
||||
// FileInfo returns an os.FileInfo for the Header.
|
||||
func (h *Header) FileInfo() os.FileInfo {
|
||||
return headerFileInfo{h}
|
||||
}
|
||||
|
||||
// headerFileInfo implements os.FileInfo.
|
||||
type headerFileInfo struct {
|
||||
h *Header
|
||||
}
|
||||
|
||||
func (fi headerFileInfo) Size() int64 { return fi.h.Size }
|
||||
func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
|
||||
func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
|
||||
func (fi headerFileInfo) Sys() interface{} { return fi.h }
|
||||
|
||||
// Name returns the base name of the file.
|
||||
func (fi headerFileInfo) Name() string {
|
||||
if fi.IsDir() {
|
||||
return path.Base(path.Clean(fi.h.Name))
|
||||
}
|
||||
return path.Base(fi.h.Name)
|
||||
}
|
||||
|
||||
// Mode returns the permission and mode bits for the headerFileInfo.
|
||||
func (fi headerFileInfo) Mode() (mode os.FileMode) {
|
||||
// Set file permission bits.
|
||||
mode = os.FileMode(fi.h.Mode).Perm()
|
||||
|
||||
// Set setuid, setgid and sticky bits.
|
||||
if fi.h.Mode&c_ISUID != 0 {
|
||||
// setuid
|
||||
mode |= os.ModeSetuid
|
||||
}
|
||||
if fi.h.Mode&c_ISGID != 0 {
|
||||
// setgid
|
||||
mode |= os.ModeSetgid
|
||||
}
|
||||
if fi.h.Mode&c_ISVTX != 0 {
|
||||
// sticky
|
||||
mode |= os.ModeSticky
|
||||
}
|
||||
|
||||
// Set file mode bits.
|
||||
// clear perm, setuid, setgid and sticky bits.
|
||||
m := os.FileMode(fi.h.Mode) &^ 07777
|
||||
if m == c_ISDIR {
|
||||
// directory
|
||||
mode |= os.ModeDir
|
||||
}
|
||||
if m == c_ISFIFO {
|
||||
// named pipe (FIFO)
|
||||
mode |= os.ModeNamedPipe
|
||||
}
|
||||
if m == c_ISLNK {
|
||||
// symbolic link
|
||||
mode |= os.ModeSymlink
|
||||
}
|
||||
if m == c_ISBLK {
|
||||
// device file
|
||||
mode |= os.ModeDevice
|
||||
}
|
||||
if m == c_ISCHR {
|
||||
// Unix character device
|
||||
mode |= os.ModeDevice
|
||||
mode |= os.ModeCharDevice
|
||||
}
|
||||
if m == c_ISSOCK {
|
||||
// Unix domain socket
|
||||
mode |= os.ModeSocket
|
||||
}
|
||||
|
||||
switch fi.h.Typeflag {
|
||||
case TypeSymlink:
|
||||
// symbolic link
|
||||
mode |= os.ModeSymlink
|
||||
case TypeChar:
|
||||
// character device node
|
||||
mode |= os.ModeDevice
|
||||
mode |= os.ModeCharDevice
|
||||
case TypeBlock:
|
||||
// block device node
|
||||
mode |= os.ModeDevice
|
||||
case TypeDir:
|
||||
// directory
|
||||
mode |= os.ModeDir
|
||||
case TypeFifo:
|
||||
// fifo node
|
||||
mode |= os.ModeNamedPipe
|
||||
}
|
||||
|
||||
return mode
|
||||
}
|
||||
|
||||
// sysStat, if non-nil, populates h from system-dependent fields of fi.
|
||||
var sysStat func(fi os.FileInfo, h *Header) error
|
||||
|
||||
// Mode constants from the tar spec.
|
||||
const (
|
||||
c_ISUID = 04000 // Set uid
|
||||
c_ISGID = 02000 // Set gid
|
||||
c_ISVTX = 01000 // Save text (sticky bit)
|
||||
c_ISDIR = 040000 // Directory
|
||||
c_ISFIFO = 010000 // FIFO
|
||||
c_ISREG = 0100000 // Regular file
|
||||
c_ISLNK = 0120000 // Symbolic link
|
||||
c_ISBLK = 060000 // Block special file
|
||||
c_ISCHR = 020000 // Character special file
|
||||
c_ISSOCK = 0140000 // Socket
|
||||
)
|
||||
|
||||
// Keywords for the PAX Extended Header
|
||||
const (
|
||||
paxAtime = "atime"
|
||||
paxCharset = "charset"
|
||||
paxComment = "comment"
|
||||
paxCtime = "ctime" // please note that ctime is not a valid pax header.
|
||||
paxCreationTime = "LIBARCHIVE.creationtime"
|
||||
paxGid = "gid"
|
||||
paxGname = "gname"
|
||||
paxLinkpath = "linkpath"
|
||||
paxMtime = "mtime"
|
||||
paxPath = "path"
|
||||
paxSize = "size"
|
||||
paxUid = "uid"
|
||||
paxUname = "uname"
|
||||
paxXattr = "SCHILY.xattr."
|
||||
paxWindows = "MSWINDOWS."
|
||||
paxNone = ""
|
||||
)
|
||||
|
||||
// FileInfoHeader creates a partially-populated Header from fi.
|
||||
// If fi describes a symlink, FileInfoHeader records link as the link target.
|
||||
// If fi describes a directory, a slash is appended to the name.
|
||||
// Because os.FileInfo's Name method returns only the base name of
|
||||
// the file it describes, it may be necessary to modify the Name field
|
||||
// of the returned header to provide the full path name of the file.
|
||||
func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
|
||||
if fi == nil {
|
||||
return nil, errors.New("tar: FileInfo is nil")
|
||||
}
|
||||
fm := fi.Mode()
|
||||
h := &Header{
|
||||
Name: fi.Name(),
|
||||
ModTime: fi.ModTime(),
|
||||
Mode: int64(fm.Perm()), // or'd with c_IS* constants later
|
||||
}
|
||||
switch {
|
||||
case fm.IsRegular():
|
||||
h.Mode |= c_ISREG
|
||||
h.Typeflag = TypeReg
|
||||
h.Size = fi.Size()
|
||||
case fi.IsDir():
|
||||
h.Typeflag = TypeDir
|
||||
h.Mode |= c_ISDIR
|
||||
h.Name += "/"
|
||||
case fm&os.ModeSymlink != 0:
|
||||
h.Typeflag = TypeSymlink
|
||||
h.Mode |= c_ISLNK
|
||||
h.Linkname = link
|
||||
case fm&os.ModeDevice != 0:
|
||||
if fm&os.ModeCharDevice != 0 {
|
||||
h.Mode |= c_ISCHR
|
||||
h.Typeflag = TypeChar
|
||||
} else {
|
||||
h.Mode |= c_ISBLK
|
||||
h.Typeflag = TypeBlock
|
||||
}
|
||||
case fm&os.ModeNamedPipe != 0:
|
||||
h.Typeflag = TypeFifo
|
||||
h.Mode |= c_ISFIFO
|
||||
case fm&os.ModeSocket != 0:
|
||||
h.Mode |= c_ISSOCK
|
||||
default:
|
||||
return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
|
||||
}
|
||||
if fm&os.ModeSetuid != 0 {
|
||||
h.Mode |= c_ISUID
|
||||
}
|
||||
if fm&os.ModeSetgid != 0 {
|
||||
h.Mode |= c_ISGID
|
||||
}
|
||||
if fm&os.ModeSticky != 0 {
|
||||
h.Mode |= c_ISVTX
|
||||
}
|
||||
// If possible, populate additional fields from OS-specific
|
||||
// FileInfo fields.
|
||||
if sys, ok := fi.Sys().(*Header); ok {
|
||||
// This FileInfo came from a Header (not the OS). Use the
|
||||
// original Header to populate all remaining fields.
|
||||
h.Uid = sys.Uid
|
||||
h.Gid = sys.Gid
|
||||
h.Uname = sys.Uname
|
||||
h.Gname = sys.Gname
|
||||
h.AccessTime = sys.AccessTime
|
||||
h.ChangeTime = sys.ChangeTime
|
||||
if sys.Xattrs != nil {
|
||||
h.Xattrs = make(map[string]string)
|
||||
for k, v := range sys.Xattrs {
|
||||
h.Xattrs[k] = v
|
||||
}
|
||||
}
|
||||
if sys.Typeflag == TypeLink {
|
||||
// hard link
|
||||
h.Typeflag = TypeLink
|
||||
h.Size = 0
|
||||
h.Linkname = sys.Linkname
|
||||
}
|
||||
}
|
||||
if sysStat != nil {
|
||||
return h, sysStat(fi, h)
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
var zeroBlock = make([]byte, blockSize)
|
||||
|
||||
// POSIX specifies a sum of the unsigned byte values, but the Sun tar uses signed byte values.
|
||||
// We compute and return both.
|
||||
func checksum(header []byte) (unsigned int64, signed int64) {
|
||||
for i := 0; i < len(header); i++ {
|
||||
if i == 148 {
|
||||
// The chksum field (header[148:156]) is special: it should be treated as space bytes.
|
||||
unsigned += ' ' * 8
|
||||
signed += ' ' * 8
|
||||
i += 7
|
||||
continue
|
||||
}
|
||||
unsigned += int64(header[i])
|
||||
signed += int64(int8(header[i]))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type slicer []byte
|
||||
|
||||
func (sp *slicer) next(n int) (b []byte) {
|
||||
s := *sp
|
||||
b, *sp = s[0:n], s[n:]
|
||||
return
|
||||
}
|
||||
|
||||
func isASCII(s string) bool {
|
||||
for _, c := range s {
|
||||
if c >= 0x80 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func toASCII(s string) string {
|
||||
if isASCII(s) {
|
||||
return s
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
for _, c := range s {
|
||||
if c < 0x80 {
|
||||
buf.WriteByte(byte(c))
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// isHeaderOnlyType checks if the given type flag is of the type that has no
|
||||
// data section even if a size is specified.
|
||||
func isHeaderOnlyType(flag byte) bool {
|
||||
switch flag {
|
||||
case TypeLink, TypeSymlink, TypeChar, TypeBlock, TypeDir, TypeFifo:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
1002
vendor/github.com/Microsoft/go-winio/archive/tar/reader.go
generated
vendored
1002
vendor/github.com/Microsoft/go-winio/archive/tar/reader.go
generated
vendored
File diff suppressed because it is too large
Load Diff
20
vendor/github.com/Microsoft/go-winio/archive/tar/stat_atim.go
generated
vendored
20
vendor/github.com/Microsoft/go-winio/archive/tar/stat_atim.go
generated
vendored
@@ -1,20 +0,0 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux dragonfly openbsd solaris
|
||||
|
||||
package tar
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func statAtime(st *syscall.Stat_t) time.Time {
|
||||
return time.Unix(st.Atim.Unix())
|
||||
}
|
||||
|
||||
func statCtime(st *syscall.Stat_t) time.Time {
|
||||
return time.Unix(st.Ctim.Unix())
|
||||
}
|
||||
20
vendor/github.com/Microsoft/go-winio/archive/tar/stat_atimespec.go
generated
vendored
20
vendor/github.com/Microsoft/go-winio/archive/tar/stat_atimespec.go
generated
vendored
@@ -1,20 +0,0 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin freebsd netbsd
|
||||
|
||||
package tar
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func statAtime(st *syscall.Stat_t) time.Time {
|
||||
return time.Unix(st.Atimespec.Unix())
|
||||
}
|
||||
|
||||
func statCtime(st *syscall.Stat_t) time.Time {
|
||||
return time.Unix(st.Ctimespec.Unix())
|
||||
}
|
||||
32
vendor/github.com/Microsoft/go-winio/archive/tar/stat_unix.go
generated
vendored
32
vendor/github.com/Microsoft/go-winio/archive/tar/stat_unix.go
generated
vendored
@@ -1,32 +0,0 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux darwin dragonfly freebsd openbsd netbsd solaris
|
||||
|
||||
package tar
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
sysStat = statUnix
|
||||
}
|
||||
|
||||
func statUnix(fi os.FileInfo, h *Header) error {
|
||||
sys, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
h.Uid = int(sys.Uid)
|
||||
h.Gid = int(sys.Gid)
|
||||
// TODO(bradfitz): populate username & group. os/user
|
||||
// doesn't cache LookupId lookups, and lacks group
|
||||
// lookup functions.
|
||||
h.AccessTime = statAtime(sys)
|
||||
h.ChangeTime = statCtime(sys)
|
||||
// TODO(bradfitz): major/minor device numbers?
|
||||
return nil
|
||||
}
|
||||
444
vendor/github.com/Microsoft/go-winio/archive/tar/writer.go
generated
vendored
444
vendor/github.com/Microsoft/go-winio/archive/tar/writer.go
generated
vendored
@@ -1,444 +0,0 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tar
|
||||
|
||||
// TODO(dsymonds):
|
||||
// - catch more errors (no first header, etc.)
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrWriteTooLong = errors.New("archive/tar: write too long")
|
||||
ErrFieldTooLong = errors.New("archive/tar: header field too long")
|
||||
ErrWriteAfterClose = errors.New("archive/tar: write after close")
|
||||
errInvalidHeader = errors.New("archive/tar: header field too long or contains invalid values")
|
||||
)
|
||||
|
||||
// A Writer provides sequential writing of a tar archive in POSIX.1 format.
|
||||
// A tar archive consists of a sequence of files.
|
||||
// Call WriteHeader to begin a new file, and then call Write to supply that file's data,
|
||||
// writing at most hdr.Size bytes in total.
|
||||
type Writer struct {
|
||||
w io.Writer
|
||||
err error
|
||||
nb int64 // number of unwritten bytes for current file entry
|
||||
pad int64 // amount of padding to write after current file entry
|
||||
closed bool
|
||||
usedBinary bool // whether the binary numeric field extension was used
|
||||
preferPax bool // use pax header instead of binary numeric header
|
||||
hdrBuff [blockSize]byte // buffer to use in writeHeader when writing a regular header
|
||||
paxHdrBuff [blockSize]byte // buffer to use in writeHeader when writing a pax header
|
||||
}
|
||||
|
||||
type formatter struct {
|
||||
err error // Last error seen
|
||||
}
|
||||
|
||||
// NewWriter creates a new Writer writing to w.
|
||||
func NewWriter(w io.Writer) *Writer { return &Writer{w: w, preferPax: true} }
|
||||
|
||||
// Flush finishes writing the current file (optional).
|
||||
func (tw *Writer) Flush() error {
|
||||
if tw.nb > 0 {
|
||||
tw.err = fmt.Errorf("archive/tar: missed writing %d bytes", tw.nb)
|
||||
return tw.err
|
||||
}
|
||||
|
||||
n := tw.nb + tw.pad
|
||||
for n > 0 && tw.err == nil {
|
||||
nr := n
|
||||
if nr > blockSize {
|
||||
nr = blockSize
|
||||
}
|
||||
var nw int
|
||||
nw, tw.err = tw.w.Write(zeroBlock[0:nr])
|
||||
n -= int64(nw)
|
||||
}
|
||||
tw.nb = 0
|
||||
tw.pad = 0
|
||||
return tw.err
|
||||
}
|
||||
|
||||
// Write s into b, terminating it with a NUL if there is room.
|
||||
func (f *formatter) formatString(b []byte, s string) {
|
||||
if len(s) > len(b) {
|
||||
f.err = ErrFieldTooLong
|
||||
return
|
||||
}
|
||||
ascii := toASCII(s)
|
||||
copy(b, ascii)
|
||||
if len(ascii) < len(b) {
|
||||
b[len(ascii)] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Encode x as an octal ASCII string and write it into b with leading zeros.
|
||||
func (f *formatter) formatOctal(b []byte, x int64) {
|
||||
s := strconv.FormatInt(x, 8)
|
||||
// leading zeros, but leave room for a NUL.
|
||||
for len(s)+1 < len(b) {
|
||||
s = "0" + s
|
||||
}
|
||||
f.formatString(b, s)
|
||||
}
|
||||
|
||||
// fitsInBase256 reports whether x can be encoded into n bytes using base-256
|
||||
// encoding. Unlike octal encoding, base-256 encoding does not require that the
|
||||
// string ends with a NUL character. Thus, all n bytes are available for output.
|
||||
//
|
||||
// If operating in binary mode, this assumes strict GNU binary mode; which means
|
||||
// that the first byte can only be either 0x80 or 0xff. Thus, the first byte is
|
||||
// equivalent to the sign bit in two's complement form.
|
||||
func fitsInBase256(n int, x int64) bool {
|
||||
var binBits = uint(n-1) * 8
|
||||
return n >= 9 || (x >= -1<<binBits && x < 1<<binBits)
|
||||
}
|
||||
|
||||
// Write x into b, as binary (GNUtar/star extension).
|
||||
func (f *formatter) formatNumeric(b []byte, x int64) {
|
||||
if fitsInBase256(len(b), x) {
|
||||
for i := len(b) - 1; i >= 0; i-- {
|
||||
b[i] = byte(x)
|
||||
x >>= 8
|
||||
}
|
||||
b[0] |= 0x80 // Highest bit indicates binary format
|
||||
return
|
||||
}
|
||||
|
||||
f.formatOctal(b, 0) // Last resort, just write zero
|
||||
f.err = ErrFieldTooLong
|
||||
}
|
||||
|
||||
var (
|
||||
minTime = time.Unix(0, 0)
|
||||
// There is room for 11 octal digits (33 bits) of mtime.
|
||||
maxTime = minTime.Add((1<<33 - 1) * time.Second)
|
||||
)
|
||||
|
||||
// WriteHeader writes hdr and prepares to accept the file's contents.
|
||||
// WriteHeader calls Flush if it is not the first header.
|
||||
// Calling after a Close will return ErrWriteAfterClose.
|
||||
func (tw *Writer) WriteHeader(hdr *Header) error {
|
||||
return tw.writeHeader(hdr, true)
|
||||
}
|
||||
|
||||
// WriteHeader writes hdr and prepares to accept the file's contents.
|
||||
// WriteHeader calls Flush if it is not the first header.
|
||||
// Calling after a Close will return ErrWriteAfterClose.
|
||||
// As this method is called internally by writePax header to allow it to
|
||||
// suppress writing the pax header.
|
||||
func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error {
|
||||
if tw.closed {
|
||||
return ErrWriteAfterClose
|
||||
}
|
||||
if tw.err == nil {
|
||||
tw.Flush()
|
||||
}
|
||||
if tw.err != nil {
|
||||
return tw.err
|
||||
}
|
||||
|
||||
// a map to hold pax header records, if any are needed
|
||||
paxHeaders := make(map[string]string)
|
||||
|
||||
// TODO(shanemhansen): we might want to use PAX headers for
|
||||
// subsecond time resolution, but for now let's just capture
|
||||
// too long fields or non ascii characters
|
||||
|
||||
var f formatter
|
||||
var header []byte
|
||||
|
||||
// We need to select which scratch buffer to use carefully,
|
||||
// since this method is called recursively to write PAX headers.
|
||||
// If allowPax is true, this is the non-recursive call, and we will use hdrBuff.
|
||||
// If allowPax is false, we are being called by writePAXHeader, and hdrBuff is
|
||||
// already being used by the non-recursive call, so we must use paxHdrBuff.
|
||||
header = tw.hdrBuff[:]
|
||||
if !allowPax {
|
||||
header = tw.paxHdrBuff[:]
|
||||
}
|
||||
copy(header, zeroBlock)
|
||||
s := slicer(header)
|
||||
|
||||
// Wrappers around formatter that automatically sets paxHeaders if the
|
||||
// argument extends beyond the capacity of the input byte slice.
|
||||
var formatString = func(b []byte, s string, paxKeyword string) {
|
||||
needsPaxHeader := paxKeyword != paxNone && len(s) > len(b) || !isASCII(s)
|
||||
if needsPaxHeader {
|
||||
paxHeaders[paxKeyword] = s
|
||||
return
|
||||
}
|
||||
f.formatString(b, s)
|
||||
}
|
||||
var formatNumeric = func(b []byte, x int64, paxKeyword string) {
|
||||
// Try octal first.
|
||||
s := strconv.FormatInt(x, 8)
|
||||
if len(s) < len(b) {
|
||||
f.formatOctal(b, x)
|
||||
return
|
||||
}
|
||||
|
||||
// If it is too long for octal, and PAX is preferred, use a PAX header.
|
||||
if paxKeyword != paxNone && tw.preferPax {
|
||||
f.formatOctal(b, 0)
|
||||
s := strconv.FormatInt(x, 10)
|
||||
paxHeaders[paxKeyword] = s
|
||||
return
|
||||
}
|
||||
|
||||
tw.usedBinary = true
|
||||
f.formatNumeric(b, x)
|
||||
}
|
||||
var formatTime = func(b []byte, t time.Time, paxKeyword string) {
|
||||
var unixTime int64
|
||||
if !t.Before(minTime) && !t.After(maxTime) {
|
||||
unixTime = t.Unix()
|
||||
}
|
||||
formatNumeric(b, unixTime, paxNone)
|
||||
|
||||
// Write a PAX header if the time didn't fit precisely.
|
||||
if paxKeyword != "" && tw.preferPax && allowPax && (t.Nanosecond() != 0 || !t.Before(minTime) || !t.After(maxTime)) {
|
||||
paxHeaders[paxKeyword] = formatPAXTime(t)
|
||||
}
|
||||
}
|
||||
|
||||
// keep a reference to the filename to allow to overwrite it later if we detect that we can use ustar longnames instead of pax
|
||||
pathHeaderBytes := s.next(fileNameSize)
|
||||
|
||||
formatString(pathHeaderBytes, hdr.Name, paxPath)
|
||||
|
||||
f.formatOctal(s.next(8), hdr.Mode) // 100:108
|
||||
formatNumeric(s.next(8), int64(hdr.Uid), paxUid) // 108:116
|
||||
formatNumeric(s.next(8), int64(hdr.Gid), paxGid) // 116:124
|
||||
formatNumeric(s.next(12), hdr.Size, paxSize) // 124:136
|
||||
formatTime(s.next(12), hdr.ModTime, paxMtime) // 136:148
|
||||
s.next(8) // chksum (148:156)
|
||||
s.next(1)[0] = hdr.Typeflag // 156:157
|
||||
|
||||
formatString(s.next(100), hdr.Linkname, paxLinkpath)
|
||||
|
||||
copy(s.next(8), []byte("ustar\x0000")) // 257:265
|
||||
formatString(s.next(32), hdr.Uname, paxUname) // 265:297
|
||||
formatString(s.next(32), hdr.Gname, paxGname) // 297:329
|
||||
formatNumeric(s.next(8), hdr.Devmajor, paxNone) // 329:337
|
||||
formatNumeric(s.next(8), hdr.Devminor, paxNone) // 337:345
|
||||
|
||||
// keep a reference to the prefix to allow to overwrite it later if we detect that we can use ustar longnames instead of pax
|
||||
prefixHeaderBytes := s.next(155)
|
||||
formatString(prefixHeaderBytes, "", paxNone) // 345:500 prefix
|
||||
|
||||
// Use the GNU magic instead of POSIX magic if we used any GNU extensions.
|
||||
if tw.usedBinary {
|
||||
copy(header[257:265], []byte("ustar \x00"))
|
||||
}
|
||||
|
||||
_, paxPathUsed := paxHeaders[paxPath]
|
||||
// try to use a ustar header when only the name is too long
|
||||
if !tw.preferPax && len(paxHeaders) == 1 && paxPathUsed {
|
||||
prefix, suffix, ok := splitUSTARPath(hdr.Name)
|
||||
if ok {
|
||||
// Since we can encode in USTAR format, disable PAX header.
|
||||
delete(paxHeaders, paxPath)
|
||||
|
||||
// Update the path fields
|
||||
formatString(pathHeaderBytes, suffix, paxNone)
|
||||
formatString(prefixHeaderBytes, prefix, paxNone)
|
||||
}
|
||||
}
|
||||
|
||||
// The chksum field is terminated by a NUL and a space.
|
||||
// This is different from the other octal fields.
|
||||
chksum, _ := checksum(header)
|
||||
f.formatOctal(header[148:155], chksum) // Never fails
|
||||
header[155] = ' '
|
||||
|
||||
// Check if there were any formatting errors.
|
||||
if f.err != nil {
|
||||
tw.err = f.err
|
||||
return tw.err
|
||||
}
|
||||
|
||||
if allowPax {
|
||||
if !hdr.AccessTime.IsZero() {
|
||||
paxHeaders[paxAtime] = formatPAXTime(hdr.AccessTime)
|
||||
}
|
||||
if !hdr.ChangeTime.IsZero() {
|
||||
paxHeaders[paxCtime] = formatPAXTime(hdr.ChangeTime)
|
||||
}
|
||||
if !hdr.CreationTime.IsZero() {
|
||||
paxHeaders[paxCreationTime] = formatPAXTime(hdr.CreationTime)
|
||||
}
|
||||
for k, v := range hdr.Xattrs {
|
||||
paxHeaders[paxXattr+k] = v
|
||||
}
|
||||
for k, v := range hdr.Winheaders {
|
||||
paxHeaders[paxWindows+k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(paxHeaders) > 0 {
|
||||
if !allowPax {
|
||||
return errInvalidHeader
|
||||
}
|
||||
if err := tw.writePAXHeader(hdr, paxHeaders); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
tw.nb = int64(hdr.Size)
|
||||
tw.pad = (blockSize - (tw.nb % blockSize)) % blockSize
|
||||
|
||||
_, tw.err = tw.w.Write(header)
|
||||
return tw.err
|
||||
}
|
||||
|
||||
func formatPAXTime(t time.Time) string {
|
||||
sec := t.Unix()
|
||||
usec := t.Nanosecond()
|
||||
s := strconv.FormatInt(sec, 10)
|
||||
if usec != 0 {
|
||||
s = fmt.Sprintf("%s.%09d", s, usec)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// splitUSTARPath splits a path according to USTAR prefix and suffix rules.
|
||||
// If the path is not splittable, then it will return ("", "", false).
|
||||
func splitUSTARPath(name string) (prefix, suffix string, ok bool) {
|
||||
length := len(name)
|
||||
if length <= fileNameSize || !isASCII(name) {
|
||||
return "", "", false
|
||||
} else if length > fileNamePrefixSize+1 {
|
||||
length = fileNamePrefixSize + 1
|
||||
} else if name[length-1] == '/' {
|
||||
length--
|
||||
}
|
||||
|
||||
i := strings.LastIndex(name[:length], "/")
|
||||
nlen := len(name) - i - 1 // nlen is length of suffix
|
||||
plen := i // plen is length of prefix
|
||||
if i <= 0 || nlen > fileNameSize || nlen == 0 || plen > fileNamePrefixSize {
|
||||
return "", "", false
|
||||
}
|
||||
return name[:i], name[i+1:], true
|
||||
}
|
||||
|
||||
// writePaxHeader writes an extended pax header to the
|
||||
// archive.
|
||||
func (tw *Writer) writePAXHeader(hdr *Header, paxHeaders map[string]string) error {
|
||||
// Prepare extended header
|
||||
ext := new(Header)
|
||||
ext.Typeflag = TypeXHeader
|
||||
// Setting ModTime is required for reader parsing to
|
||||
// succeed, and seems harmless enough.
|
||||
ext.ModTime = hdr.ModTime
|
||||
// The spec asks that we namespace our pseudo files
|
||||
// with the current pid. However, this results in differing outputs
|
||||
// for identical inputs. As such, the constant 0 is now used instead.
|
||||
// golang.org/issue/12358
|
||||
dir, file := path.Split(hdr.Name)
|
||||
fullName := path.Join(dir, "PaxHeaders.0", file)
|
||||
|
||||
ascii := toASCII(fullName)
|
||||
if len(ascii) > 100 {
|
||||
ascii = ascii[:100]
|
||||
}
|
||||
ext.Name = ascii
|
||||
// Construct the body
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Keys are sorted before writing to body to allow deterministic output.
|
||||
var keys []string
|
||||
for k := range paxHeaders {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
fmt.Fprint(&buf, formatPAXRecord(k, paxHeaders[k]))
|
||||
}
|
||||
|
||||
ext.Size = int64(len(buf.Bytes()))
|
||||
if err := tw.writeHeader(ext, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tw.Write(buf.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tw.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// formatPAXRecord formats a single PAX record, prefixing it with the
|
||||
// appropriate length.
|
||||
func formatPAXRecord(k, v string) string {
|
||||
const padding = 3 // Extra padding for ' ', '=', and '\n'
|
||||
size := len(k) + len(v) + padding
|
||||
size += len(strconv.Itoa(size))
|
||||
record := fmt.Sprintf("%d %s=%s\n", size, k, v)
|
||||
|
||||
// Final adjustment if adding size field increased the record size.
|
||||
if len(record) != size {
|
||||
size = len(record)
|
||||
record = fmt.Sprintf("%d %s=%s\n", size, k, v)
|
||||
}
|
||||
return record
|
||||
}
|
||||
|
||||
// Write writes to the current entry in the tar archive.
|
||||
// Write returns the error ErrWriteTooLong if more than
|
||||
// hdr.Size bytes are written after WriteHeader.
|
||||
func (tw *Writer) Write(b []byte) (n int, err error) {
|
||||
if tw.closed {
|
||||
err = ErrWriteAfterClose
|
||||
return
|
||||
}
|
||||
overwrite := false
|
||||
if int64(len(b)) > tw.nb {
|
||||
b = b[0:tw.nb]
|
||||
overwrite = true
|
||||
}
|
||||
n, err = tw.w.Write(b)
|
||||
tw.nb -= int64(n)
|
||||
if err == nil && overwrite {
|
||||
err = ErrWriteTooLong
|
||||
return
|
||||
}
|
||||
tw.err = err
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes the tar archive, flushing any unwritten
|
||||
// data to the underlying writer.
|
||||
func (tw *Writer) Close() error {
|
||||
if tw.err != nil || tw.closed {
|
||||
return tw.err
|
||||
}
|
||||
tw.Flush()
|
||||
tw.closed = true
|
||||
if tw.err != nil {
|
||||
return tw.err
|
||||
}
|
||||
|
||||
// trailer: two zero blocks
|
||||
for i := 0; i < 2; i++ {
|
||||
_, tw.err = tw.w.Write(zeroBlock)
|
||||
if tw.err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return tw.err
|
||||
}
|
||||
68
vendor/github.com/Microsoft/go-winio/backuptar/strconv.go
generated
vendored
Normal file
68
vendor/github.com/Microsoft/go-winio/backuptar/strconv.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
package backuptar
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Functions copied from https://github.com/golang/go/blob/master/src/archive/tar/strconv.go
|
||||
// as we need to manage the LIBARCHIVE.creationtime PAXRecord manually.
|
||||
// Idea taken from containerd which did the same thing.
|
||||
|
||||
// parsePAXTime takes a string of the form %d.%d as described in the PAX
|
||||
// specification. Note that this implementation allows for negative timestamps,
|
||||
// which is allowed for by the PAX specification, but not always portable.
|
||||
func parsePAXTime(s string) (time.Time, error) {
|
||||
const maxNanoSecondDigits = 9
|
||||
|
||||
// Split string into seconds and sub-seconds parts.
|
||||
ss, sn := s, ""
|
||||
if pos := strings.IndexByte(s, '.'); pos >= 0 {
|
||||
ss, sn = s[:pos], s[pos+1:]
|
||||
}
|
||||
|
||||
// Parse the seconds.
|
||||
secs, err := strconv.ParseInt(ss, 10, 64)
|
||||
if err != nil {
|
||||
return time.Time{}, tar.ErrHeader
|
||||
}
|
||||
if len(sn) == 0 {
|
||||
return time.Unix(secs, 0), nil // No sub-second values
|
||||
}
|
||||
|
||||
// Parse the nanoseconds.
|
||||
if strings.Trim(sn, "0123456789") != "" {
|
||||
return time.Time{}, tar.ErrHeader
|
||||
}
|
||||
if len(sn) < maxNanoSecondDigits {
|
||||
sn += strings.Repeat("0", maxNanoSecondDigits-len(sn)) // Right pad
|
||||
} else {
|
||||
sn = sn[:maxNanoSecondDigits] // Right truncate
|
||||
}
|
||||
nsecs, _ := strconv.ParseInt(sn, 10, 64) // Must succeed
|
||||
if len(ss) > 0 && ss[0] == '-' {
|
||||
return time.Unix(secs, -1*nsecs), nil // Negative correction
|
||||
}
|
||||
return time.Unix(secs, nsecs), nil
|
||||
}
|
||||
|
||||
// formatPAXTime converts ts into a time of the form %d.%d as described in the
|
||||
// PAX specification. This function is capable of negative timestamps.
|
||||
func formatPAXTime(ts time.Time) (s string) {
|
||||
secs, nsecs := ts.Unix(), ts.Nanosecond()
|
||||
if nsecs == 0 {
|
||||
return strconv.FormatInt(secs, 10)
|
||||
}
|
||||
|
||||
// If seconds is negative, then perform correction.
|
||||
sign := ""
|
||||
if secs < 0 {
|
||||
sign = "-" // Remember sign
|
||||
secs = -(secs + 1) // Add a second to secs
|
||||
nsecs = -(nsecs - 1e9) // Take that second away from nsecs
|
||||
}
|
||||
return strings.TrimRight(fmt.Sprintf("%s%d.%09d", sign, secs, nsecs), "0")
|
||||
}
|
||||
67
vendor/github.com/Microsoft/go-winio/backuptar/tar.go
generated
vendored
67
vendor/github.com/Microsoft/go-winio/backuptar/tar.go
generated
vendored
@@ -3,6 +3,7 @@
|
||||
package backuptar
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
"github.com/Microsoft/go-winio/archive/tar" // until archive/tar supports pax extensions in its interface
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -32,11 +33,13 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
hdrFileAttributes = "fileattr"
|
||||
hdrSecurityDescriptor = "sd"
|
||||
hdrRawSecurityDescriptor = "rawsd"
|
||||
hdrMountPoint = "mountpoint"
|
||||
hdrEaPrefix = "xattr."
|
||||
hdrFileAttributes = "MSWINDOWS.fileattr"
|
||||
hdrSecurityDescriptor = "MSWINDOWS.sd"
|
||||
hdrRawSecurityDescriptor = "MSWINDOWS.rawsd"
|
||||
hdrMountPoint = "MSWINDOWS.mountpoint"
|
||||
hdrEaPrefix = "MSWINDOWS.xattr."
|
||||
|
||||
hdrCreationTime = "LIBARCHIVE.creationtime"
|
||||
)
|
||||
|
||||
func writeZeroes(w io.Writer, count int64) error {
|
||||
@@ -86,16 +89,17 @@ func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
|
||||
// BasicInfoHeader creates a tar header from basic file information.
|
||||
func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header {
|
||||
hdr := &tar.Header{
|
||||
Name: filepath.ToSlash(name),
|
||||
Size: size,
|
||||
Typeflag: tar.TypeReg,
|
||||
ModTime: time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()),
|
||||
ChangeTime: time.Unix(0, fileInfo.ChangeTime.Nanoseconds()),
|
||||
AccessTime: time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()),
|
||||
CreationTime: time.Unix(0, fileInfo.CreationTime.Nanoseconds()),
|
||||
Winheaders: make(map[string]string),
|
||||
Format: tar.FormatPAX,
|
||||
Name: filepath.ToSlash(name),
|
||||
Size: size,
|
||||
Typeflag: tar.TypeReg,
|
||||
ModTime: time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()),
|
||||
ChangeTime: time.Unix(0, fileInfo.ChangeTime.Nanoseconds()),
|
||||
AccessTime: time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()),
|
||||
PAXRecords: make(map[string]string),
|
||||
}
|
||||
hdr.Winheaders[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes)
|
||||
hdr.PAXRecords[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes)
|
||||
hdr.PAXRecords[hdrCreationTime] = formatPAXTime(time.Unix(0, fileInfo.CreationTime.Nanoseconds()))
|
||||
|
||||
if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
|
||||
hdr.Mode |= c_ISDIR
|
||||
@@ -155,7 +159,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Winheaders[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd)
|
||||
hdr.PAXRecords[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd)
|
||||
|
||||
case winio.BackupReparseData:
|
||||
hdr.Mode |= c_ISLNK
|
||||
@@ -166,7 +170,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size
|
||||
return err
|
||||
}
|
||||
if rp.IsMountPoint {
|
||||
hdr.Winheaders[hdrMountPoint] = "1"
|
||||
hdr.PAXRecords[hdrMountPoint] = "1"
|
||||
}
|
||||
hdr.Linkname = rp.Target
|
||||
|
||||
@@ -183,7 +187,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size
|
||||
// Use base64 encoding for the binary value. Note that there
|
||||
// is no way to encode the EA's flags, since their use doesn't
|
||||
// make any sense for persisted EAs.
|
||||
hdr.Winheaders[hdrEaPrefix+ea.Name] = base64.StdEncoding.EncodeToString(ea.Value)
|
||||
hdr.PAXRecords[hdrEaPrefix+ea.Name] = base64.StdEncoding.EncodeToString(ea.Value)
|
||||
}
|
||||
|
||||
case winio.BackupAlternateData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
|
||||
@@ -254,6 +258,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size
|
||||
}
|
||||
if (bhdr.Attributes & winio.StreamSparseAttributes) == 0 {
|
||||
hdr = &tar.Header{
|
||||
Format: hdr.Format,
|
||||
Name: name + altName,
|
||||
Mode: hdr.Mode,
|
||||
Typeflag: tar.TypeReg,
|
||||
@@ -293,12 +298,13 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win
|
||||
size = hdr.Size
|
||||
}
|
||||
fileInfo = &winio.FileBasicInfo{
|
||||
LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()),
|
||||
LastWriteTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
|
||||
ChangeTime: syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()),
|
||||
CreationTime: syscall.NsecToFiletime(hdr.CreationTime.UnixNano()),
|
||||
LastAccessTime: windows.NsecToFiletime(hdr.AccessTime.UnixNano()),
|
||||
LastWriteTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()),
|
||||
ChangeTime: windows.NsecToFiletime(hdr.ChangeTime.UnixNano()),
|
||||
// Default to ModTime, we'll pull hdrCreationTime below if present
|
||||
CreationTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()),
|
||||
}
|
||||
if attrStr, ok := hdr.Winheaders[hdrFileAttributes]; ok {
|
||||
if attrStr, ok := hdr.PAXRecords[hdrFileAttributes]; ok {
|
||||
attr, err := strconv.ParseUint(attrStr, 10, 32)
|
||||
if err != nil {
|
||||
return "", 0, nil, err
|
||||
@@ -309,6 +315,13 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win
|
||||
fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
|
||||
}
|
||||
}
|
||||
if creationTimeStr, ok := hdr.PAXRecords[hdrCreationTime]; ok {
|
||||
creationTime, err := parsePAXTime(creationTimeStr)
|
||||
if err != nil {
|
||||
return "", 0, nil, err
|
||||
}
|
||||
fileInfo.CreationTime = windows.NsecToFiletime(creationTime.UnixNano())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -321,13 +334,13 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (
|
||||
var err error
|
||||
// Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written
|
||||
// by this library will have raw binary for the security descriptor.
|
||||
if sddl, ok := hdr.Winheaders[hdrSecurityDescriptor]; ok {
|
||||
if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok {
|
||||
sd, err = winio.SddlToSecurityDescriptor(sddl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if sdraw, ok := hdr.Winheaders[hdrRawSecurityDescriptor]; ok {
|
||||
if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok {
|
||||
sd, err = base64.StdEncoding.DecodeString(sdraw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -348,7 +361,7 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (
|
||||
}
|
||||
}
|
||||
var eas []winio.ExtendedAttribute
|
||||
for k, v := range hdr.Winheaders {
|
||||
for k, v := range hdr.PAXRecords {
|
||||
if !strings.HasPrefix(k, hdrEaPrefix) {
|
||||
continue
|
||||
}
|
||||
@@ -380,7 +393,7 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (
|
||||
}
|
||||
}
|
||||
if hdr.Typeflag == tar.TypeSymlink {
|
||||
_, isMountPoint := hdr.Winheaders[hdrMountPoint]
|
||||
_, isMountPoint := hdr.PAXRecords[hdrMountPoint]
|
||||
rp := winio.ReparsePoint{
|
||||
Target: filepath.FromSlash(hdr.Linkname),
|
||||
IsMountPoint: isMountPoint,
|
||||
|
||||
36
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
36
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
@@ -5,21 +5,14 @@ package winio
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx
|
||||
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle
|
||||
|
||||
const (
|
||||
fileBasicInfo = 0
|
||||
fileIDInfo = 0x12
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// FileBasicInfo contains file access time and file attributes information.
|
||||
type FileBasicInfo struct {
|
||||
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
|
||||
CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime
|
||||
FileAttributes uint32
|
||||
pad uint32 // padding
|
||||
}
|
||||
@@ -27,7 +20,7 @@ type FileBasicInfo struct {
|
||||
// GetFileBasicInfo retrieves times and attributes for a file.
|
||||
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
||||
bi := &FileBasicInfo{}
|
||||
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
|
||||
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
|
||||
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(f)
|
||||
@@ -36,13 +29,32 @@ func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
||||
|
||||
// SetFileBasicInfo sets times and attributes for a file.
|
||||
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
|
||||
if err := setFileInformationByHandle(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
|
||||
if err := windows.SetFileInformationByHandle(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
|
||||
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileStandardInfo contains extended information for the file.
|
||||
// FILE_STANDARD_INFO in WinBase.h
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info
|
||||
type FileStandardInfo struct {
|
||||
AllocationSize, EndOfFile int64
|
||||
NumberOfLinks uint32
|
||||
DeletePending, Directory bool
|
||||
}
|
||||
|
||||
// GetFileStandardInfo retrieves ended information for the file.
|
||||
func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) {
|
||||
si := &FileStandardInfo{}
|
||||
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileStandardInfo, (*byte)(unsafe.Pointer(si)), uint32(unsafe.Sizeof(*si))); err != nil {
|
||||
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(f)
|
||||
return si, nil
|
||||
}
|
||||
|
||||
// FileIDInfo contains the volume serial number and file ID for a file. This pair should be
|
||||
// unique on a system.
|
||||
type FileIDInfo struct {
|
||||
@@ -53,7 +65,7 @@ type FileIDInfo struct {
|
||||
// GetFileID retrieves the unique (volume, file ID) pair for a file.
|
||||
func GetFileID(f *os.File) (*FileIDInfo, error) {
|
||||
fileID := &FileIDInfo{}
|
||||
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileIDInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil {
|
||||
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileIdInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil {
|
||||
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(f)
|
||||
|
||||
6
vendor/github.com/Microsoft/go-winio/go.mod
generated
vendored
6
vendor/github.com/Microsoft/go-winio/go.mod
generated
vendored
@@ -3,7 +3,7 @@ module github.com/Microsoft/go-winio
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/sirupsen/logrus v1.4.1
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
|
||||
)
|
||||
|
||||
20
vendor/github.com/Microsoft/go-winio/go.sum
generated
vendored
20
vendor/github.com/Microsoft/go-winio/go.sum
generated
vendored
@@ -1,18 +1,14 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
2
vendor/github.com/Microsoft/go-winio/hvsock.go
generated
vendored
2
vendor/github.com/Microsoft/go-winio/hvsock.go
generated
vendored
@@ -1,3 +1,5 @@
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
import (
|
||||
|
||||
21
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
21
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
@@ -182,13 +182,14 @@ func (s pipeAddress) String() string {
|
||||
}
|
||||
|
||||
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
|
||||
func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) {
|
||||
func tryDialPipe(ctx context.Context, path *string, access uint32) (syscall.Handle, error) {
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return syscall.Handle(0), ctx.Err()
|
||||
default:
|
||||
h, err := createFile(*path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||
h, err := createFile(*path, access, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
@@ -197,7 +198,7 @@ func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) {
|
||||
}
|
||||
// Wait 10 msec and try again. This is a rather simplistic
|
||||
// view, as we always try each 10 milliseconds.
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,7 +211,7 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||
if timeout != nil {
|
||||
absTimeout = time.Now().Add(*timeout)
|
||||
} else {
|
||||
absTimeout = time.Now().Add(time.Second * 2)
|
||||
absTimeout = time.Now().Add(2 * time.Second)
|
||||
}
|
||||
ctx, _ := context.WithDeadline(context.Background(), absTimeout)
|
||||
conn, err := DialPipeContext(ctx, path)
|
||||
@@ -223,9 +224,15 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||
// DialPipeContext attempts to connect to a named pipe by `path` until `ctx`
|
||||
// cancellation or timeout.
|
||||
func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
|
||||
return DialPipeAccess(ctx, path, syscall.GENERIC_READ|syscall.GENERIC_WRITE)
|
||||
}
|
||||
|
||||
// DialPipeAccess attempts to connect to a named pipe by `path` with `access` until `ctx`
|
||||
// cancellation or timeout.
|
||||
func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) {
|
||||
var err error
|
||||
var h syscall.Handle
|
||||
h, err = tryDialPipe(ctx, &path)
|
||||
h, err = tryDialPipe(ctx, &path, access)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -422,10 +429,10 @@ type PipeConfig struct {
|
||||
// when the pipe is in message mode.
|
||||
MessageMode bool
|
||||
|
||||
// InputBufferSize specifies the size the input buffer, in bytes.
|
||||
// InputBufferSize specifies the size of the input buffer, in bytes.
|
||||
InputBufferSize int32
|
||||
|
||||
// OutputBufferSize specifies the size the input buffer, in bytes.
|
||||
// OutputBufferSize specifies the size of the output buffer, in bytes.
|
||||
OutputBufferSize int32
|
||||
}
|
||||
|
||||
|
||||
2
vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go
generated
vendored
2
vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go
generated
vendored
@@ -1,3 +1,5 @@
|
||||
// +build windows
|
||||
|
||||
// Package guid provides a GUID type. The backing structure for a GUID is
|
||||
// identical to that used by the golang.org/x/sys/windows GUID type.
|
||||
// There are two main binary encodings used for a GUID, the big-endian encoding,
|
||||
|
||||
161
vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go
generated
vendored
Normal file
161
vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// +build windows
|
||||
|
||||
package security
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
accessMask uint32
|
||||
accessMode uint32
|
||||
desiredAccess uint32
|
||||
inheritMode uint32
|
||||
objectType uint32
|
||||
shareMode uint32
|
||||
securityInformation uint32
|
||||
trusteeForm uint32
|
||||
trusteeType uint32
|
||||
|
||||
explicitAccess struct {
|
||||
accessPermissions accessMask
|
||||
accessMode accessMode
|
||||
inheritance inheritMode
|
||||
trustee trustee
|
||||
}
|
||||
|
||||
trustee struct {
|
||||
multipleTrustee *trustee
|
||||
multipleTrusteeOperation int32
|
||||
trusteeForm trusteeForm
|
||||
trusteeType trusteeType
|
||||
name uintptr
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
accessMaskDesiredPermission accessMask = 1 << 31 // GENERIC_READ
|
||||
|
||||
accessModeGrant accessMode = 1
|
||||
|
||||
desiredAccessReadControl desiredAccess = 0x20000
|
||||
desiredAccessWriteDac desiredAccess = 0x40000
|
||||
|
||||
gvmga = "GrantVmGroupAccess:"
|
||||
|
||||
inheritModeNoInheritance inheritMode = 0x0
|
||||
inheritModeSubContainersAndObjectsInherit inheritMode = 0x3
|
||||
|
||||
objectTypeFileObject objectType = 0x1
|
||||
|
||||
securityInformationDACL securityInformation = 0x4
|
||||
|
||||
shareModeRead shareMode = 0x1
|
||||
shareModeWrite shareMode = 0x2
|
||||
|
||||
sidVmGroup = "S-1-5-83-0"
|
||||
|
||||
trusteeFormIsSid trusteeForm = 0
|
||||
|
||||
trusteeTypeWellKnownGroup trusteeType = 5
|
||||
)
|
||||
|
||||
// GrantVMGroupAccess sets the DACL for a specified file or directory to
|
||||
// include Grant ACE entries for the VM Group SID. This is a golang re-
|
||||
// implementation of the same function in vmcompute, just not exported in
|
||||
// RS5. Which kind of sucks. Sucks a lot :/
|
||||
func GrantVmGroupAccess(name string) error {
|
||||
// Stat (to determine if `name` is a directory).
|
||||
s, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "%s os.Stat %s", gvmga, name)
|
||||
}
|
||||
|
||||
// Get a handle to the file/directory. Must defer Close on success.
|
||||
fd, err := createFile(name, s.IsDir())
|
||||
if err != nil {
|
||||
return err // Already wrapped
|
||||
}
|
||||
defer syscall.CloseHandle(fd)
|
||||
|
||||
// Get the current DACL and Security Descriptor. Must defer LocalFree on success.
|
||||
ot := objectTypeFileObject
|
||||
si := securityInformationDACL
|
||||
sd := uintptr(0)
|
||||
origDACL := uintptr(0)
|
||||
if err := getSecurityInfo(fd, uint32(ot), uint32(si), nil, nil, &origDACL, nil, &sd); err != nil {
|
||||
return errors.Wrapf(err, "%s GetSecurityInfo %s", gvmga, name)
|
||||
}
|
||||
defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd)))
|
||||
|
||||
// Generate a new DACL which is the current DACL with the required ACEs added.
|
||||
// Must defer LocalFree on success.
|
||||
newDACL, err := generateDACLWithAcesAdded(name, s.IsDir(), origDACL)
|
||||
if err != nil {
|
||||
return err // Already wrapped
|
||||
}
|
||||
defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(newDACL)))
|
||||
|
||||
// And finally use SetSecurityInfo to apply the updated DACL.
|
||||
if err := setSecurityInfo(fd, uint32(ot), uint32(si), uintptr(0), uintptr(0), newDACL, uintptr(0)); err != nil {
|
||||
return errors.Wrapf(err, "%s SetSecurityInfo %s", gvmga, name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createFile is a helper function to call [Nt]CreateFile to get a handle to
|
||||
// the file or directory.
|
||||
func createFile(name string, isDir bool) (syscall.Handle, error) {
|
||||
namep := syscall.StringToUTF16(name)
|
||||
da := uint32(desiredAccessReadControl | desiredAccessWriteDac)
|
||||
sm := uint32(shareModeRead | shareModeWrite)
|
||||
fa := uint32(syscall.FILE_ATTRIBUTE_NORMAL)
|
||||
if isDir {
|
||||
fa = uint32(fa | syscall.FILE_FLAG_BACKUP_SEMANTICS)
|
||||
}
|
||||
fd, err := syscall.CreateFile(&namep[0], da, sm, nil, syscall.OPEN_EXISTING, fa, 0)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "%s syscall.CreateFile %s", gvmga, name)
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
// generateDACLWithAcesAdded generates a new DACL with the two needed ACEs added.
|
||||
// The caller is responsible for LocalFree of the returned DACL on success.
|
||||
func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintptr, error) {
|
||||
// Generate pointers to the SIDs based on the string SIDs
|
||||
sid, err := syscall.StringToSid(sidVmGroup)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "%s syscall.StringToSid %s %s", gvmga, name, sidVmGroup)
|
||||
}
|
||||
|
||||
inheritance := inheritModeNoInheritance
|
||||
if isDir {
|
||||
inheritance = inheritModeSubContainersAndObjectsInherit
|
||||
}
|
||||
|
||||
eaArray := []explicitAccess{
|
||||
explicitAccess{
|
||||
accessPermissions: accessMaskDesiredPermission,
|
||||
accessMode: accessModeGrant,
|
||||
inheritance: inheritance,
|
||||
trustee: trustee{
|
||||
trusteeForm: trusteeFormIsSid,
|
||||
trusteeType: trusteeTypeWellKnownGroup,
|
||||
name: uintptr(unsafe.Pointer(sid)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
modifiedDACL := uintptr(0)
|
||||
if err := setEntriesInAcl(uintptr(uint32(1)), uintptr(unsafe.Pointer(&eaArray[0])), origDACL, &modifiedDACL); err != nil {
|
||||
return 0, errors.Wrapf(err, "%s SetEntriesInAcl %s", gvmga, name)
|
||||
}
|
||||
|
||||
return modifiedDACL, nil
|
||||
}
|
||||
7
vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go
generated
vendored
Normal file
7
vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package security
|
||||
|
||||
//go:generate go run mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go
|
||||
|
||||
//sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) [failretval!=0] = advapi32.GetSecurityInfo
|
||||
//sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) [failretval!=0] = advapi32.SetSecurityInfo
|
||||
//sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) [failretval!=0] = advapi32.SetEntriesInAclW
|
||||
70
vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go
generated
vendored
Normal file
70
vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
// Code generated by 'go generate'; DO NOT EDIT.
|
||||
|
||||
package security
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||
|
||||
procGetSecurityInfo = modadvapi32.NewProc("GetSecurityInfo")
|
||||
procSetEntriesInAclW = modadvapi32.NewProc("SetEntriesInAclW")
|
||||
procSetSecurityInfo = modadvapi32.NewProc("SetSecurityInfo")
|
||||
)
|
||||
|
||||
func getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) {
|
||||
r1, _, e1 := syscall.Syscall9(procGetSecurityInfo.Addr(), 8, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(unsafe.Pointer(ppsidOwner)), uintptr(unsafe.Pointer(ppsidGroup)), uintptr(unsafe.Pointer(ppDacl)), uintptr(unsafe.Pointer(ppSacl)), uintptr(unsafe.Pointer(ppSecurityDescriptor)), 0)
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procSetEntriesInAclW.Addr(), 4, uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl)), 0, 0)
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) {
|
||||
r1, _, e1 := syscall.Syscall9(procSetSecurityInfo.Addr(), 7, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl), 0, 0)
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
5
vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
5
vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
@@ -28,8 +28,9 @@ const (
|
||||
|
||||
ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300
|
||||
|
||||
SeBackupPrivilege = "SeBackupPrivilege"
|
||||
SeRestorePrivilege = "SeRestorePrivilege"
|
||||
SeBackupPrivilege = "SeBackupPrivilege"
|
||||
SeRestorePrivilege = "SeRestorePrivilege"
|
||||
SeSecurityPrivilege = "SeSecurityPrivilege"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
2
vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
2
vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
@@ -1,3 +1,3 @@
|
||||
package winio
|
||||
|
||||
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go
|
||||
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go
|
||||
|
||||
323
vendor/github.com/Microsoft/go-winio/vhd/vhd.go
generated
vendored
Normal file
323
vendor/github.com/Microsoft/go-winio/vhd/vhd.go
generated
vendored
Normal file
@@ -0,0 +1,323 @@
|
||||
// +build windows
|
||||
|
||||
package vhd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/go-winio/pkg/guid"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//go:generate go run mksyscall_windows.go -output zvhd_windows.go vhd.go
|
||||
|
||||
//sys createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) [failretval != 0] = virtdisk.CreateVirtualDisk
|
||||
//sys openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) [failretval != 0] = virtdisk.OpenVirtualDisk
|
||||
//sys attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (err error) [failretval != 0] = virtdisk.AttachVirtualDisk
|
||||
//sys detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (err error) [failretval != 0] = virtdisk.DetachVirtualDisk
|
||||
//sys getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (err error) [failretval != 0] = virtdisk.GetVirtualDiskPhysicalPath
|
||||
|
||||
type (
|
||||
CreateVirtualDiskFlag uint32
|
||||
VirtualDiskFlag uint32
|
||||
AttachVirtualDiskFlag uint32
|
||||
DetachVirtualDiskFlag uint32
|
||||
VirtualDiskAccessMask uint32
|
||||
)
|
||||
|
||||
type VirtualStorageType struct {
|
||||
DeviceID uint32
|
||||
VendorID guid.GUID
|
||||
}
|
||||
|
||||
type CreateVersion2 struct {
|
||||
UniqueID guid.GUID
|
||||
MaximumSize uint64
|
||||
BlockSizeInBytes uint32
|
||||
SectorSizeInBytes uint32
|
||||
PhysicalSectorSizeInByte uint32
|
||||
ParentPath *uint16 // string
|
||||
SourcePath *uint16 // string
|
||||
OpenFlags uint32
|
||||
ParentVirtualStorageType VirtualStorageType
|
||||
SourceVirtualStorageType VirtualStorageType
|
||||
ResiliencyGUID guid.GUID
|
||||
}
|
||||
|
||||
type CreateVirtualDiskParameters struct {
|
||||
Version uint32 // Must always be set to 2
|
||||
Version2 CreateVersion2
|
||||
}
|
||||
|
||||
type OpenVersion2 struct {
|
||||
GetInfoOnly bool
|
||||
ReadOnly bool
|
||||
ResiliencyGUID guid.GUID
|
||||
}
|
||||
|
||||
type OpenVirtualDiskParameters struct {
|
||||
Version uint32 // Must always be set to 2
|
||||
Version2 OpenVersion2
|
||||
}
|
||||
|
||||
type AttachVersion2 struct {
|
||||
RestrictedOffset uint64
|
||||
RestrictedLength uint64
|
||||
}
|
||||
|
||||
type AttachVirtualDiskParameters struct {
|
||||
Version uint32 // Must always be set to 2
|
||||
Version2 AttachVersion2
|
||||
}
|
||||
|
||||
const (
|
||||
VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 0x3
|
||||
|
||||
// Access Mask for opening a VHD
|
||||
VirtualDiskAccessNone VirtualDiskAccessMask = 0x00000000
|
||||
VirtualDiskAccessAttachRO VirtualDiskAccessMask = 0x00010000
|
||||
VirtualDiskAccessAttachRW VirtualDiskAccessMask = 0x00020000
|
||||
VirtualDiskAccessDetach VirtualDiskAccessMask = 0x00040000
|
||||
VirtualDiskAccessGetInfo VirtualDiskAccessMask = 0x00080000
|
||||
VirtualDiskAccessCreate VirtualDiskAccessMask = 0x00100000
|
||||
VirtualDiskAccessMetaOps VirtualDiskAccessMask = 0x00200000
|
||||
VirtualDiskAccessRead VirtualDiskAccessMask = 0x000d0000
|
||||
VirtualDiskAccessAll VirtualDiskAccessMask = 0x003f0000
|
||||
VirtualDiskAccessWritable VirtualDiskAccessMask = 0x00320000
|
||||
|
||||
// Flags for creating a VHD
|
||||
CreateVirtualDiskFlagNone CreateVirtualDiskFlag = 0x0
|
||||
CreateVirtualDiskFlagFullPhysicalAllocation CreateVirtualDiskFlag = 0x1
|
||||
CreateVirtualDiskFlagPreventWritesToSourceDisk CreateVirtualDiskFlag = 0x2
|
||||
CreateVirtualDiskFlagDoNotCopyMetadataFromParent CreateVirtualDiskFlag = 0x4
|
||||
CreateVirtualDiskFlagCreateBackingStorage CreateVirtualDiskFlag = 0x8
|
||||
CreateVirtualDiskFlagUseChangeTrackingSourceLimit CreateVirtualDiskFlag = 0x10
|
||||
CreateVirtualDiskFlagPreserveParentChangeTrackingState CreateVirtualDiskFlag = 0x20
|
||||
CreateVirtualDiskFlagVhdSetUseOriginalBackingStorage CreateVirtualDiskFlag = 0x40
|
||||
CreateVirtualDiskFlagSparseFile CreateVirtualDiskFlag = 0x80
|
||||
CreateVirtualDiskFlagPmemCompatible CreateVirtualDiskFlag = 0x100
|
||||
CreateVirtualDiskFlagSupportCompressedVolumes CreateVirtualDiskFlag = 0x200
|
||||
|
||||
// Flags for opening a VHD
|
||||
OpenVirtualDiskFlagNone VirtualDiskFlag = 0x00000000
|
||||
OpenVirtualDiskFlagNoParents VirtualDiskFlag = 0x00000001
|
||||
OpenVirtualDiskFlagBlankFile VirtualDiskFlag = 0x00000002
|
||||
OpenVirtualDiskFlagBootDrive VirtualDiskFlag = 0x00000004
|
||||
OpenVirtualDiskFlagCachedIO VirtualDiskFlag = 0x00000008
|
||||
OpenVirtualDiskFlagCustomDiffChain VirtualDiskFlag = 0x00000010
|
||||
OpenVirtualDiskFlagParentCachedIO VirtualDiskFlag = 0x00000020
|
||||
OpenVirtualDiskFlagVhdsetFileOnly VirtualDiskFlag = 0x00000040
|
||||
OpenVirtualDiskFlagIgnoreRelativeParentLocator VirtualDiskFlag = 0x00000080
|
||||
OpenVirtualDiskFlagNoWriteHardening VirtualDiskFlag = 0x00000100
|
||||
OpenVirtualDiskFlagSupportCompressedVolumes VirtualDiskFlag = 0x00000200
|
||||
|
||||
// Flags for attaching a VHD
|
||||
AttachVirtualDiskFlagNone AttachVirtualDiskFlag = 0x00000000
|
||||
AttachVirtualDiskFlagReadOnly AttachVirtualDiskFlag = 0x00000001
|
||||
AttachVirtualDiskFlagNoDriveLetter AttachVirtualDiskFlag = 0x00000002
|
||||
AttachVirtualDiskFlagPermanentLifetime AttachVirtualDiskFlag = 0x00000004
|
||||
AttachVirtualDiskFlagNoLocalHost AttachVirtualDiskFlag = 0x00000008
|
||||
AttachVirtualDiskFlagNoSecurityDescriptor AttachVirtualDiskFlag = 0x00000010
|
||||
AttachVirtualDiskFlagBypassDefaultEncryptionPolicy AttachVirtualDiskFlag = 0x00000020
|
||||
AttachVirtualDiskFlagNonPnp AttachVirtualDiskFlag = 0x00000040
|
||||
AttachVirtualDiskFlagRestrictedRange AttachVirtualDiskFlag = 0x00000080
|
||||
AttachVirtualDiskFlagSinglePartition AttachVirtualDiskFlag = 0x00000100
|
||||
AttachVirtualDiskFlagRegisterVolume AttachVirtualDiskFlag = 0x00000200
|
||||
|
||||
// Flags for detaching a VHD
|
||||
DetachVirtualDiskFlagNone DetachVirtualDiskFlag = 0x0
|
||||
)
|
||||
|
||||
// CreateVhdx is a helper function to create a simple vhdx file at the given path using
|
||||
// default values.
|
||||
func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error {
|
||||
params := CreateVirtualDiskParameters{
|
||||
Version: 2,
|
||||
Version2: CreateVersion2{
|
||||
MaximumSize: uint64(maxSizeInGb) * 1024 * 1024 * 1024,
|
||||
BlockSizeInBytes: blockSizeInMb * 1024 * 1024,
|
||||
},
|
||||
}
|
||||
|
||||
handle, err := CreateVirtualDisk(path, VirtualDiskAccessNone, CreateVirtualDiskFlagNone, ¶ms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := syscall.CloseHandle(handle); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetachVirtualDisk detaches a virtual hard disk by handle.
|
||||
func DetachVirtualDisk(handle syscall.Handle) (err error) {
|
||||
if err := detachVirtualDisk(handle, 0, 0); err != nil {
|
||||
return errors.Wrap(err, "failed to detach virtual disk")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetachVhd detaches a vhd found at `path`.
|
||||
func DetachVhd(path string) error {
|
||||
handle, err := OpenVirtualDisk(
|
||||
path,
|
||||
VirtualDiskAccessNone,
|
||||
OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.CloseHandle(handle)
|
||||
return DetachVirtualDisk(handle)
|
||||
}
|
||||
|
||||
// AttachVirtualDisk attaches a virtual hard disk for use.
|
||||
func AttachVirtualDisk(handle syscall.Handle, attachVirtualDiskFlag AttachVirtualDiskFlag, parameters *AttachVirtualDiskParameters) (err error) {
|
||||
// Supports both version 1 and 2 of the attach parameters as version 2 wasn't present in RS5.
|
||||
if err := attachVirtualDisk(
|
||||
handle,
|
||||
nil,
|
||||
uint32(attachVirtualDiskFlag),
|
||||
0,
|
||||
parameters,
|
||||
nil,
|
||||
); err != nil {
|
||||
return errors.Wrap(err, "failed to attach virtual disk")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachVhd attaches a virtual hard disk at `path` for use. Attaches using version 2
|
||||
// of the ATTACH_VIRTUAL_DISK_PARAMETERS.
|
||||
func AttachVhd(path string) (err error) {
|
||||
handle, err := OpenVirtualDisk(
|
||||
path,
|
||||
VirtualDiskAccessNone,
|
||||
OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer syscall.CloseHandle(handle)
|
||||
params := AttachVirtualDiskParameters{Version: 2}
|
||||
if err := AttachVirtualDisk(
|
||||
handle,
|
||||
AttachVirtualDiskFlagNone,
|
||||
¶ms,
|
||||
); err != nil {
|
||||
return errors.Wrap(err, "failed to attach virtual disk")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenVirtualDisk obtains a handle to a VHD opened with supplied access mask and flags.
|
||||
func OpenVirtualDisk(vhdPath string, virtualDiskAccessMask VirtualDiskAccessMask, openVirtualDiskFlags VirtualDiskFlag) (syscall.Handle, error) {
|
||||
parameters := OpenVirtualDiskParameters{Version: 2}
|
||||
handle, err := OpenVirtualDiskWithParameters(
|
||||
vhdPath,
|
||||
virtualDiskAccessMask,
|
||||
openVirtualDiskFlags,
|
||||
¶meters,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// OpenVirtualDiskWithParameters obtains a handle to a VHD opened with supplied access mask, flags and parameters.
|
||||
func OpenVirtualDiskWithParameters(vhdPath string, virtualDiskAccessMask VirtualDiskAccessMask, openVirtualDiskFlags VirtualDiskFlag, parameters *OpenVirtualDiskParameters) (syscall.Handle, error) {
|
||||
var (
|
||||
handle syscall.Handle
|
||||
defaultType VirtualStorageType
|
||||
)
|
||||
if parameters.Version != 2 {
|
||||
return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version)
|
||||
}
|
||||
if err := openVirtualDisk(
|
||||
&defaultType,
|
||||
vhdPath,
|
||||
uint32(virtualDiskAccessMask),
|
||||
uint32(openVirtualDiskFlags),
|
||||
parameters,
|
||||
&handle,
|
||||
); err != nil {
|
||||
return 0, errors.Wrap(err, "failed to open virtual disk")
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// CreateVirtualDisk creates a virtual harddisk and returns a handle to the disk.
|
||||
func CreateVirtualDisk(path string, virtualDiskAccessMask VirtualDiskAccessMask, createVirtualDiskFlags CreateVirtualDiskFlag, parameters *CreateVirtualDiskParameters) (syscall.Handle, error) {
|
||||
var (
|
||||
handle syscall.Handle
|
||||
defaultType VirtualStorageType
|
||||
)
|
||||
if parameters.Version != 2 {
|
||||
return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version)
|
||||
}
|
||||
|
||||
if err := createVirtualDisk(
|
||||
&defaultType,
|
||||
path,
|
||||
uint32(virtualDiskAccessMask),
|
||||
nil,
|
||||
uint32(createVirtualDiskFlags),
|
||||
0,
|
||||
parameters,
|
||||
nil,
|
||||
&handle,
|
||||
); err != nil {
|
||||
return handle, errors.Wrap(err, "failed to create virtual disk")
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// GetVirtualDiskPhysicalPath takes a handle to a virtual hard disk and returns the physical
|
||||
// path of the disk on the machine. This path is in the form \\.\PhysicalDriveX where X is an integer
|
||||
// that represents the particular enumeration of the physical disk on the caller's system.
|
||||
func GetVirtualDiskPhysicalPath(handle syscall.Handle) (_ string, err error) {
|
||||
var (
|
||||
diskPathSizeInBytes uint32 = 256 * 2 // max path length 256 wide chars
|
||||
diskPhysicalPathBuf [256]uint16
|
||||
)
|
||||
if err := getVirtualDiskPhysicalPath(
|
||||
handle,
|
||||
&diskPathSizeInBytes,
|
||||
&diskPhysicalPathBuf[0],
|
||||
); err != nil {
|
||||
return "", errors.Wrap(err, "failed to get disk physical path")
|
||||
}
|
||||
return windows.UTF16ToString(diskPhysicalPathBuf[:]), nil
|
||||
}
|
||||
|
||||
// CreateDiffVhd is a helper function to create a differencing virtual disk.
|
||||
func CreateDiffVhd(diffVhdPath, baseVhdPath string, blockSizeInMB uint32) error {
|
||||
// Setting `ParentPath` is how to signal to create a differencing disk.
|
||||
createParams := &CreateVirtualDiskParameters{
|
||||
Version: 2,
|
||||
Version2: CreateVersion2{
|
||||
ParentPath: windows.StringToUTF16Ptr(baseVhdPath),
|
||||
BlockSizeInBytes: blockSizeInMB * 1024 * 1024,
|
||||
OpenFlags: uint32(OpenVirtualDiskFlagCachedIO),
|
||||
},
|
||||
}
|
||||
|
||||
vhdHandle, err := CreateVirtualDisk(
|
||||
diffVhdPath,
|
||||
VirtualDiskAccessNone,
|
||||
CreateVirtualDiskFlagNone,
|
||||
createParams,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create differencing vhd: %s", err)
|
||||
}
|
||||
if err := syscall.CloseHandle(vhdHandle); err != nil {
|
||||
return fmt.Errorf("failed to close differencing vhd handle: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user