mirror of
https://github.com/containers/skopeo.git
synced 2026-01-30 13:58:48 +00:00
Compare commits
111 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
875bb42594 | ||
|
|
1186cc6bce | ||
|
|
311f61f1aa | ||
|
|
796c9cc041 | ||
|
|
49084d2cd8 | ||
|
|
8b904e908e | ||
|
|
23183072fb | ||
|
|
3be97ce281 | ||
|
|
b46506c077 | ||
|
|
49d9fa9faf | ||
|
|
77363128e1 | ||
|
|
59a452276b | ||
|
|
0f363498c2 | ||
|
|
a2dccca2e6 | ||
|
|
27b77f2bde | ||
|
|
6eda759dd2 | ||
|
|
de71408294 | ||
|
|
13cd098079 | ||
|
|
697ef59525 | ||
|
|
e4b79d7741 | ||
|
|
bf24ce9ff2 | ||
|
|
162bbab3a6 | ||
|
|
cf19643e76 | ||
|
|
afc18ceed3 | ||
|
|
004519f143 | ||
|
|
9db60ec007 | ||
|
|
cb74933b41 | ||
|
|
8fb455174d | ||
|
|
7f4db3db9d | ||
|
|
96cdfac7d9 | ||
|
|
a4476c358c | ||
|
|
1391aae0a5 | ||
|
|
042f481629 | ||
|
|
3518c50688 | ||
|
|
327f87d79b | ||
|
|
bd8ed664d5 | ||
|
|
b51707d50d | ||
|
|
2c84bc232c | ||
|
|
bb49923af4 | ||
|
|
639aabbaf3 | ||
|
|
cd58349b25 | ||
|
|
4b79ed7d7d | ||
|
|
2858904e4b | ||
|
|
15296d9876 | ||
|
|
923c58a8ee | ||
|
|
43726bbc27 | ||
|
|
1bf18b7ef8 | ||
|
|
df4d82b960 | ||
|
|
d32c56b47f | ||
|
|
6007e792e4 | ||
|
|
77f881e61c | ||
|
|
5aa06a51f4 | ||
|
|
e422e44fca | ||
|
|
f6a84289eb | ||
|
|
2689eb367f | ||
|
|
c5b45c6c49 | ||
|
|
037f518146 | ||
|
|
c582c4844f | ||
|
|
2046bfdaaa | ||
|
|
25868f17c0 | ||
|
|
e7dc5e79f2 | ||
|
|
3606b2d1de | ||
|
|
f03d0401c1 | ||
|
|
5c82c7728f | ||
|
|
37d801c90b | ||
|
|
c3f65951bc | ||
|
|
d94015466f | ||
|
|
1d24e657fa | ||
|
|
4dcd28df92 | ||
|
|
789ee8bea9 | ||
|
|
8a88191c84 | ||
|
|
69728fdf93 | ||
|
|
904c745bb0 | ||
|
|
47066f2d77 | ||
|
|
fab344c335 | ||
|
|
adfa1d4e49 | ||
|
|
002978258c | ||
|
|
05a2ed4921 | ||
|
|
e9535f868b | ||
|
|
fa86297c36 | ||
|
|
2bb6f27d13 | ||
|
|
f90725d80c | ||
|
|
644074cbb4 | ||
|
|
83416068d3 | ||
|
|
a3adf36db6 | ||
|
|
6510f1011b | ||
|
|
e7b7be5734 | ||
|
|
1e01e38459 | ||
|
|
942cd6ec58 | ||
|
|
a902709e14 | ||
|
|
41de7f2f66 | ||
|
|
c264cec359 | ||
|
|
2b357d8276 | ||
|
|
4acc9f0d2c | ||
|
|
c2732cb15d | ||
|
|
49f709576a | ||
|
|
7885162a35 | ||
|
|
01e58f8e25 | ||
|
|
36d860ebce | ||
|
|
c8777f3bf7 | ||
|
|
8f64c0412f | ||
|
|
985d4c09ae | ||
|
|
8182255d22 | ||
|
|
11b5989872 | ||
|
|
2144a37c21 | ||
|
|
9c9a9f3a1f | ||
|
|
60c98cacde | ||
|
|
116e75fbfd | ||
|
|
89ecd5a4c0 | ||
|
|
fc81803bfa | ||
|
|
119eeb83a7 |
10
.cirrus.yml
10
.cirrus.yml
@@ -23,12 +23,12 @@ env:
|
||||
####
|
||||
#### 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"
|
||||
FEDORA_NAME: "fedora-35"
|
||||
PRIOR_FEDORA_NAME: "fedora-34"
|
||||
UBUNTU_NAME: "ubuntu-2110"
|
||||
|
||||
# Google-cloud VM Images
|
||||
IMAGE_SUFFIX: "c6431352024203264"
|
||||
IMAGE_SUFFIX: "c4764556961513472"
|
||||
FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}"
|
||||
PRIOR_FEDORA_CACHE_IMAGE_NAME: "prior-fedora-${IMAGE_SUFFIX}"
|
||||
UBUNTU_CACHE_IMAGE_NAME: "ubuntu-${IMAGE_SUFFIX}"
|
||||
@@ -90,7 +90,7 @@ osx_task:
|
||||
export PATH=$GOPATH/bin:$PATH
|
||||
brew update
|
||||
brew install gpgme go go-md2man
|
||||
go get -u golang.org/x/lint/golint
|
||||
go install golang.org/x/lint/golint@latest
|
||||
test_script: |
|
||||
export PATH=$GOPATH/bin:$PATH
|
||||
go version
|
||||
|
||||
23
.github/workflows/check_cirrus_cron.yml
vendored
23
.github/workflows/check_cirrus_cron.yml
vendored
@@ -22,8 +22,8 @@ env:
|
||||
# Debug-mode can reveal secrets, only enable by a secret value.
|
||||
# Ref: https://help.github.com/en/actions/configuring-and-managing-workflows/managing-a-workflow-run#enabling-step-debug-logging
|
||||
ACTIONS_STEP_DEBUG: '${{ secrets.ACTIONS_STEP_DEBUG }}'
|
||||
# Use same destination addresses from podman repository
|
||||
FAILMAILCSV: './_podman/contrib/cirrus/cron-fail_addrs.csv'
|
||||
# CSV listing of e-mail addresses for delivery failure or error notices
|
||||
RCPTCSV: rh.container.bot@gmail.com,podman-monitor@lists.podman.io
|
||||
# Filename for table of cron-name to build-id data
|
||||
# (must be in $GITHUB_WORKSPACE/artifacts/)
|
||||
NAME_ID_FILEPATH: './artifacts/name_id.txt'
|
||||
@@ -69,10 +69,6 @@ jobs:
|
||||
) > ./artifacts/email_body.txt
|
||||
|
||||
- if: steps.cron.outputs.failures > 0
|
||||
id: mailto
|
||||
run: printf "::set-output name=csv::%s\n" $(cat "$FAILMAILCSV")
|
||||
|
||||
- if: steps.mailto.outputs.csv != ''
|
||||
name: Send failure notification e-mail
|
||||
# Ref: https://github.com/dawidd6/action-send-mail
|
||||
uses: dawidd6/action-send-mail@v2.2.2
|
||||
@@ -82,7 +78,7 @@ jobs:
|
||||
username: ${{secrets.ACTION_MAIL_USERNAME}}
|
||||
password: ${{secrets.ACTION_MAIL_PASSWORD}}
|
||||
subject: Cirrus-CI cron build failures on ${{github.repository}}
|
||||
to: ${{steps.mailto.outputs.csv}}
|
||||
to: ${{env.RCPTCSV}}
|
||||
from: ${{secrets.ACTION_MAIL_SENDER}}
|
||||
body: file://./artifacts/email_body.txt
|
||||
|
||||
@@ -91,3 +87,16 @@ jobs:
|
||||
with:
|
||||
name: ${{ github.job }}_artifacts
|
||||
path: artifacts/*
|
||||
|
||||
- if: failure()
|
||||
name: Send error notification e-mail
|
||||
uses: dawidd6/action-send-mail@v2.2.2
|
||||
with:
|
||||
server_address: ${{secrets.ACTION_MAIL_SERVER}}
|
||||
server_port: 465
|
||||
username: ${{secrets.ACTION_MAIL_USERNAME}}
|
||||
password: ${{secrets.ACTION_MAIL_PASSWORD}}
|
||||
subject: Github workflow error on ${{github.repository}}
|
||||
to: ${{env.RCPTCSV}}
|
||||
from: ${{secrets.ACTION_MAIL_SENDER}}
|
||||
body: "Job failed: https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"
|
||||
|
||||
3
Makefile
3
Makefile
@@ -112,6 +112,9 @@ endif
|
||||
# use source debugging tools like delve.
|
||||
all: bin/skopeo docs
|
||||
|
||||
codespell:
|
||||
codespell -S Makefile,build,buildah,buildah.spec,imgtype,copy,AUTHORS,bin,vendor,.git,go.sum,CHANGELOG.md,changelog.txt,seccomp.json,.cirrus.yml,"*.xz,*.gz,*.tar,*.tgz,*ico,*.png,*.1,*.5,*.orig,*.rej" -L fpr,uint,iff,od,ERRO -w
|
||||
|
||||
help:
|
||||
@echo "Usage: make <target>"
|
||||
@echo
|
||||
|
||||
@@ -7,10 +7,12 @@ import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
commonFlag "github.com/containers/common/pkg/flag"
|
||||
"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/pkg/cli"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
encconfig "github.com/containers/ocicrypt/config"
|
||||
@@ -24,16 +26,19 @@ type copyOptions struct {
|
||||
srcImage *imageOptions
|
||||
destImage *imageDestOptions
|
||||
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
|
||||
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
|
||||
signPassphraseFile string // Path pointing to a passphrase file when signing
|
||||
digestFile string // Write digest to this file
|
||||
format commonFlag.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
|
||||
multiArch commonFlag.OptionalString // How to handle multi architecture images
|
||||
preserveDigests bool // Preserve digests during copy
|
||||
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) *cobra.Command {
|
||||
@@ -71,16 +76,40 @@ See skopeo(1) section "IMAGE NAMES" for the expected format
|
||||
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.Var(commonFlag.NewOptionalStringValue(&opts.multiArch), "multi-arch", `How to handle multi-architecture images (system, all, or index-only)`)
|
||||
flags.BoolVar(&opts.preserveDigests, "preserve-digests", false, "Preserve digests of images and lists")
|
||||
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.signPassphraseFile, "sign-passphrase-file", "", "File that contains a passphrase for the --sign-by key")
|
||||
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 in the destination (default is manifest type of source, with fallbacks)`)
|
||||
flags.VarP(commonFlag.NewOptionalStringValue(&opts.format), "format", "f", `MANIFEST TYPE (oci, v2s1, or v2s2) to use in the destination (default is manifest type of source, with fallbacks)`)
|
||||
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
|
||||
}
|
||||
|
||||
// parseMultiArch parses the list processing selection
|
||||
// It returns the copy.ImageListSelection to use with image.Copy option
|
||||
func parseMultiArch(multiArch string) (copy.ImageListSelection, error) {
|
||||
switch multiArch {
|
||||
case "system":
|
||||
return copy.CopySystemImage, nil
|
||||
case "all":
|
||||
return copy.CopyAllImages, nil
|
||||
// There is no CopyNoImages value in copy.ImageListSelection, but because we
|
||||
// don't provide an option to select a set of images to copy, we can use
|
||||
// CopySpecificImages.
|
||||
case "index-only":
|
||||
return copy.CopySpecificImages, nil
|
||||
// We don't expose CopySpecificImages other than index-only above, because
|
||||
// we currently don't provide an option to choose the images to copy. That
|
||||
// could be added in the future.
|
||||
default:
|
||||
return copy.CopySystemImage, fmt.Errorf("unknown multi-arch option %q. Choose one of the supported options: 'system', 'all', or 'index-only'", multiArch)
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *copyOptions) run(args []string, stdout io.Writer) error {
|
||||
if len(args) != 2 {
|
||||
return errorShouldDisplayUsage{errors.New("Exactly two arguments expected")}
|
||||
@@ -117,8 +146,8 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error {
|
||||
}
|
||||
|
||||
var manifestType string
|
||||
if opts.format.present {
|
||||
manifestType, err = parseManifestFormat(opts.format.value)
|
||||
if opts.format.Present() {
|
||||
manifestType, err = parseManifestFormat(opts.format.Value())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -142,7 +171,17 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error {
|
||||
if opts.quiet {
|
||||
stdout = nil
|
||||
}
|
||||
|
||||
imageListSelection := copy.CopySystemImage
|
||||
if opts.multiArch.Present() && opts.all {
|
||||
return fmt.Errorf("Cannot use --all and --multi-arch flags together")
|
||||
}
|
||||
if opts.multiArch.Present() {
|
||||
imageListSelection, err = parseMultiArch(opts.multiArch.Value())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if opts.all {
|
||||
imageListSelection = copy.CopyAllImages
|
||||
}
|
||||
@@ -183,15 +222,22 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error {
|
||||
decConfig = cc.DecryptConfig
|
||||
}
|
||||
|
||||
passphrase, err := cli.ReadPassphraseFile(opts.signPassphraseFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return retry.RetryIfNecessary(ctx, func() error {
|
||||
manifestBytes, err := copy.Image(ctx, policyContext, destRef, srcRef, ©.Options{
|
||||
RemoveSignatures: opts.removeSignatures,
|
||||
SignBy: opts.signByFingerprint,
|
||||
SignPassphrase: passphrase,
|
||||
ReportWriter: stdout,
|
||||
SourceCtx: sourceCtx,
|
||||
DestinationCtx: destinationCtx,
|
||||
ForceManifestMIMEType: manifestType,
|
||||
ImageListSelection: imageListSelection,
|
||||
PreserveDigests: opts.preserveDigests,
|
||||
OciDecryptConfig: decConfig,
|
||||
OciEncryptLayers: encLayers,
|
||||
OciEncryptConfig: encConfig,
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOptionalBoolSet(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
input string
|
||||
accepted bool
|
||||
value bool
|
||||
}{
|
||||
// Valid inputs documented for strconv.ParseBool == flag.BoolVar
|
||||
{"1", true, true},
|
||||
{"t", true, true},
|
||||
{"T", true, true},
|
||||
{"TRUE", true, true},
|
||||
{"true", true, true},
|
||||
{"True", true, true},
|
||||
{"0", true, false},
|
||||
{"f", true, false},
|
||||
{"F", true, false},
|
||||
{"FALSE", true, false},
|
||||
{"false", true, false},
|
||||
{"False", true, false},
|
||||
// A few invalid inputs
|
||||
{"", false, false},
|
||||
{"yes", false, false},
|
||||
{"no", false, false},
|
||||
{"2", false, false},
|
||||
} {
|
||||
var ob optionalBool
|
||||
v := internalNewOptionalBoolValue(&ob)
|
||||
require.False(t, ob.present)
|
||||
err := v.Set(c.input)
|
||||
if c.accepted {
|
||||
assert.NoError(t, err, c.input)
|
||||
assert.Equal(t, c.value, ob.value)
|
||||
} else {
|
||||
assert.Error(t, err, c.input)
|
||||
assert.False(t, ob.present) // Just to be extra paranoid.
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing actually explicitly says that .Set() is never called when the flag is not present on the command line;
|
||||
// so, check that it is not being called, at least in the straightforward case (it's not possible to test that it
|
||||
// is not called in any possible situation).
|
||||
var globalOB, commandOB optionalBool
|
||||
actionRun := false
|
||||
app := &cobra.Command{
|
||||
Use: "app",
|
||||
}
|
||||
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
|
||||
},
|
||||
}
|
||||
optionalBoolFlag(cmd.Flags(), &commandOB, "command-OB", "")
|
||||
app.AddCommand(cmd)
|
||||
app.SetArgs([]string{"cmd"})
|
||||
err := app.Execute()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, actionRun)
|
||||
}
|
||||
|
||||
func TestOptionalBoolString(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
input optionalBool
|
||||
expected string
|
||||
}{
|
||||
{optionalBool{present: true, value: true}, "true"},
|
||||
{optionalBool{present: true, value: false}, "false"},
|
||||
{optionalBool{present: false, value: true}, ""},
|
||||
{optionalBool{present: false, value: false}, ""},
|
||||
} {
|
||||
var ob optionalBool
|
||||
v := internalNewOptionalBoolValue(&ob)
|
||||
ob = c.input
|
||||
res := v.String()
|
||||
assert.Equal(t, c.expected, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionalBoolIsBoolFlag(t *testing.T) {
|
||||
// IsBoolFlag means that the argument value must either be part of the same argument, with =;
|
||||
// if there is no =, the value is set to true.
|
||||
// This differs form other flags, where the argument is required and may be either separated with = or supplied in the next argument.
|
||||
for _, c := range []struct {
|
||||
input []string
|
||||
expectedOB optionalBool
|
||||
expectedArgs []string
|
||||
}{
|
||||
{[]string{"1", "2"}, optionalBool{present: false}, []string{"1", "2"}}, // Flag not present
|
||||
{[]string{"--OB=true", "1", "2"}, optionalBool{present: true, value: true}, []string{"1", "2"}}, // --OB=true
|
||||
{[]string{"--OB=false", "1", "2"}, optionalBool{present: true, value: false}, []string{"1", "2"}}, // --OB=false
|
||||
{[]string{"--OB", "true", "1", "2"}, optionalBool{present: true, value: true}, []string{"true", "1", "2"}}, // --OB true
|
||||
{[]string{"--OB", "false", "1", "2"}, optionalBool{present: true, value: true}, []string{"false", "1", "2"}}, // --OB false
|
||||
} {
|
||||
var ob optionalBool
|
||||
actionRun := false
|
||||
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, args)
|
||||
actionRun = true
|
||||
return nil
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionalStringSet(t *testing.T) {
|
||||
// Really just a smoke test, but differentiating between not present and empty.
|
||||
for _, c := range []string{"", "hello"} {
|
||||
var os optionalString
|
||||
v := newOptionalStringValue(&os)
|
||||
require.False(t, os.present)
|
||||
err := v.Set(c)
|
||||
assert.NoError(t, err, c)
|
||||
assert.Equal(t, c, os.value)
|
||||
}
|
||||
|
||||
// Nothing actually explicitly says that .Set() is never called when the flag is not present on the command line;
|
||||
// so, check that it is not being called, at least in the straightforward case (it's not possible to test that it
|
||||
// is not called in any possible situation).
|
||||
var globalOS, commandOS optionalString
|
||||
actionRun := false
|
||||
app := &cobra.Command{
|
||||
Use: "app",
|
||||
}
|
||||
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
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
func TestOptionalStringString(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
input optionalString
|
||||
expected string
|
||||
}{
|
||||
{optionalString{present: true, value: "hello"}, "hello"},
|
||||
{optionalString{present: true, value: ""}, ""},
|
||||
{optionalString{present: false, value: "hello"}, ""},
|
||||
{optionalString{present: false, value: ""}, ""},
|
||||
} {
|
||||
var os optionalString
|
||||
v := newOptionalStringValue(&os)
|
||||
os = c.input
|
||||
res := v.String()
|
||||
assert.Equal(t, c.expected, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionalStringIsBoolFlag(t *testing.T) {
|
||||
// NOTE: optionalStringValue does not implement IsBoolFlag!
|
||||
// IsBoolFlag means that the argument value must either be part of the same argument, with =;
|
||||
// if there is no =, the value is set to true.
|
||||
// This differs form other flags, where the argument is required and may be either separated with = or supplied in the next argument.
|
||||
for _, c := range []struct {
|
||||
input []string
|
||||
expectedOS optionalString
|
||||
expectedArgs []string
|
||||
}{
|
||||
{[]string{"1", "2"}, optionalString{present: false}, []string{"1", "2"}}, // Flag not present
|
||||
{[]string{"--OS=hello", "1", "2"}, optionalString{present: true, value: "hello"}, []string{"1", "2"}}, // --OS=true
|
||||
{[]string{"--OS=", "1", "2"}, optionalString{present: true, value: ""}, []string{"1", "2"}}, // --OS=false
|
||||
{[]string{"--OS", "hello", "1", "2"}, optionalString{present: true, value: "hello"}, []string{"1", "2"}}, // --OS true
|
||||
{[]string{"--OS", "", "1", "2"}, optionalString{present: true, value: ""}, []string{"1", "2"}}, // --OS false
|
||||
} {
|
||||
var os optionalString
|
||||
actionRun := false
|
||||
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, args)
|
||||
actionRun = true
|
||||
return nil
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -24,12 +24,13 @@ import (
|
||||
)
|
||||
|
||||
type inspectOptions struct {
|
||||
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
|
||||
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
|
||||
doNotListTags bool // Do not list all tags available in the same repository
|
||||
}
|
||||
|
||||
func inspectCmd(global *globalOptions) *cobra.Command {
|
||||
@@ -60,6 +61,7 @@ See skopeo(1) section "IMAGE NAMES" for the expected format
|
||||
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.BoolVarP(&opts.doNotListTags, "no-tags", "n", false, "Do not list the available tags from the repository in the output")
|
||||
flags.AddFlagSet(&sharedFlags)
|
||||
flags.AddFlagSet(&imageFlags)
|
||||
flags.AddFlagSet(&retryFlags)
|
||||
@@ -192,7 +194,7 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
||||
if dockerRef := img.Reference().DockerReference(); dockerRef != nil {
|
||||
outputData.Name = dockerRef.Name()
|
||||
}
|
||||
if img.Reference().Transport() == docker.Transport {
|
||||
if !opts.doNotListTags && img.Reference().Transport() == docker.Transport {
|
||||
sys, err := opts.image.newSystemContext()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/containers/common/pkg/auth"
|
||||
commonFlag "github.com/containers/common/pkg/flag"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
type loginOptions struct {
|
||||
global *globalOptions
|
||||
loginOpts auth.LoginOptions
|
||||
tlsVerify optionalBool
|
||||
tlsVerify commonFlag.OptionalBool
|
||||
}
|
||||
|
||||
func loginCmd(global *globalOptions) *cobra.Command {
|
||||
@@ -28,7 +29,7 @@ func loginCmd(global *globalOptions) *cobra.Command {
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
flags := cmd.Flags()
|
||||
optionalBoolFlag(flags, &opts.tlsVerify, "tls-verify", "require HTTPS and verify certificates when accessing the registry")
|
||||
commonFlag.OptionalBoolFlag(flags, &opts.tlsVerify, "tls-verify", "require HTTPS and verify certificates when accessing the registry")
|
||||
flags.AddFlagSet(auth.GetLoginFlags(&opts.loginOpts))
|
||||
return cmd
|
||||
}
|
||||
@@ -40,8 +41,8 @@ func (opts *loginOptions) run(args []string, stdout io.Writer) error {
|
||||
opts.loginOpts.Stdin = os.Stdin
|
||||
opts.loginOpts.AcceptRepositories = true
|
||||
sys := opts.global.newSystemContext()
|
||||
if opts.tlsVerify.present {
|
||||
sys.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.tlsVerify.value)
|
||||
if opts.tlsVerify.Present() {
|
||||
sys.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.tlsVerify.Value())
|
||||
}
|
||||
return auth.Login(ctx, sys, &opts.loginOpts, args)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/containers/common/pkg/auth"
|
||||
commonFlag "github.com/containers/common/pkg/flag"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
type logoutOptions struct {
|
||||
global *globalOptions
|
||||
logoutOpts auth.LogoutOptions
|
||||
tlsVerify optionalBool
|
||||
tlsVerify commonFlag.OptionalBool
|
||||
}
|
||||
|
||||
func logoutCmd(global *globalOptions) *cobra.Command {
|
||||
@@ -27,7 +28,7 @@ func logoutCmd(global *globalOptions) *cobra.Command {
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
flags := cmd.Flags()
|
||||
optionalBoolFlag(flags, &opts.tlsVerify, "tls-verify", "require HTTPS and verify certificates when accessing the registry")
|
||||
commonFlag.OptionalBoolFlag(flags, &opts.tlsVerify, "tls-verify", "require HTTPS and verify certificates when accessing the registry")
|
||||
flags.AddFlagSet(auth.GetLogoutFlags(&opts.logoutOpts))
|
||||
return cmd
|
||||
}
|
||||
@@ -36,8 +37,8 @@ func (opts *logoutOptions) run(args []string, stdout io.Writer) error {
|
||||
opts.logoutOpts.Stdout = stdout
|
||||
opts.logoutOpts.AcceptRepositories = true
|
||||
sys := opts.global.newSystemContext()
|
||||
if opts.tlsVerify.present {
|
||||
sys.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.tlsVerify.value)
|
||||
if opts.tlsVerify.Present() {
|
||||
sys.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.tlsVerify.Value())
|
||||
}
|
||||
return auth.Logout(sys, &opts.logoutOpts, args)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
commonFlag "github.com/containers/common/pkg/flag"
|
||||
"github.com/containers/image/v5/signature"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/skopeo/version"
|
||||
@@ -20,17 +22,32 @@ 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:)
|
||||
policyPath string // Path to a signature verification policy file
|
||||
insecurePolicy bool // Use an "allow everything" signature verification policy
|
||||
registriesDirPath string // Path to a "registries.d" registry configuration directory
|
||||
overrideArch string // Architecture to use for choosing images, instead of the runtime one
|
||||
overrideOS string // OS to use for choosing images, instead of the runtime one
|
||||
overrideVariant string // Architecture variant to use for choosing images, instead of the runtime one
|
||||
commandTimeout time.Duration // Timeout for the command execution
|
||||
registriesConfPath string // Path to the "registries.conf" file
|
||||
tmpDir string // Path to use for big temporary files
|
||||
debug bool // Enable debug output
|
||||
tlsVerify commonFlag.OptionalBool // Require HTTPS and verify certificates (for docker: and docker-daemon:)
|
||||
policyPath string // Path to a signature verification policy file
|
||||
insecurePolicy bool // Use an "allow everything" signature verification policy
|
||||
registriesDirPath string // Path to a "registries.d" registry configuration directory
|
||||
overrideArch string // Architecture to use for choosing images, instead of the runtime one
|
||||
overrideOS string // OS to use for choosing images, instead of the runtime one
|
||||
overrideVariant string // Architecture variant to use for choosing images, instead of the runtime one
|
||||
commandTimeout time.Duration // Timeout for the command execution
|
||||
registriesConfPath string // Path to the "registries.conf" file
|
||||
tmpDir string // Path to use for big temporary files
|
||||
}
|
||||
|
||||
// requireSubcommand returns an error if no sub command is provided
|
||||
// This was copied from podman: `github.com/containers/podman/cmd/podman/validate/args.go
|
||||
// Some small style changes to match skopeo were applied, but try to apply any
|
||||
// bugfixes there first.
|
||||
func requireSubcommand(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
suggestions := cmd.SuggestionsFor(args[0])
|
||||
if len(suggestions) == 0 {
|
||||
return fmt.Errorf("Unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information", cmd.CommandPath(), args[0])
|
||||
}
|
||||
return fmt.Errorf("Unrecognized command `%[1]s %[2]s`\n\nDid you mean this?\n\t%[3]s\n\nTry '%[1]s --help' for more information", cmd.CommandPath(), args[0], strings.Join(suggestions, "\n\t"))
|
||||
}
|
||||
return fmt.Errorf("Missing command '%[1]s COMMAND'\nTry '%[1]s --help' for more information", cmd.CommandPath())
|
||||
}
|
||||
|
||||
// createApp returns a cobra.Command, and the underlying globalOptions object, to be run or tested.
|
||||
@@ -40,6 +57,7 @@ func createApp() (*cobra.Command, *globalOptions) {
|
||||
rootCommand := &cobra.Command{
|
||||
Use: "skopeo",
|
||||
Long: "Various operations with container images and container image registries",
|
||||
RunE: requireSubcommand,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return opts.before(cmd)
|
||||
},
|
||||
@@ -78,7 +96,7 @@ func createApp() (*cobra.Command, *globalOptions) {
|
||||
logrus.Fatal("unable to mark registries-conf flag as hidden")
|
||||
}
|
||||
rootCommand.PersistentFlags().StringVar(&opts.tmpDir, "tmpdir", "", "directory used to store temporary files")
|
||||
flag := optionalBoolFlag(rootCommand.Flags(), &opts.tlsVerify, "tls-verify", "Require HTTPS and verify certificates when accessing the registry")
|
||||
flag := commonFlag.OptionalBoolFlag(rootCommand.Flags(), &opts.tlsVerify, "tls-verify", "Require HTTPS and verify certificates when accessing the registry")
|
||||
flag.Hidden = true
|
||||
rootCommand.AddCommand(
|
||||
copyCmd(&opts),
|
||||
@@ -88,6 +106,7 @@ func createApp() (*cobra.Command, *globalOptions) {
|
||||
loginCmd(&opts),
|
||||
logoutCmd(&opts),
|
||||
manifestDigestCmd(),
|
||||
proxyCmd(&opts),
|
||||
syncCmd(&opts),
|
||||
standaloneSignCmd(),
|
||||
standaloneVerifyCmd(),
|
||||
@@ -102,7 +121,7 @@ func (opts *globalOptions) before(cmd *cobra.Command) error {
|
||||
if opts.debug {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
if opts.tlsVerify.present {
|
||||
if opts.tlsVerify.Present() {
|
||||
logrus.Warn("'--tls-verify' is deprecated, please set this on the specific subcommand")
|
||||
}
|
||||
return nil
|
||||
@@ -159,8 +178,8 @@ func (opts *globalOptions) newSystemContext() *types.SystemContext {
|
||||
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)
|
||||
if opts.tlsVerify.Present() {
|
||||
ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.tlsVerify.Value())
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
734
cmd/skopeo/proxy.go
Normal file
734
cmd/skopeo/proxy.go
Normal file
@@ -0,0 +1,734 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package main
|
||||
|
||||
/*
|
||||
This code is currently only intended to be used by ostree
|
||||
to fetch content via containers. The API is subject
|
||||
to change. A goal however is to stabilize the API
|
||||
eventually as a full out-of-process interface to the
|
||||
core containers/image library functionality.
|
||||
|
||||
To use this command, in a parent process create a
|
||||
`socketpair()` of type `SOCK_SEQPACKET`. Fork
|
||||
off this command, and pass one half of the socket
|
||||
pair to the child. Providing it on stdin (fd 0)
|
||||
is the expected default.
|
||||
|
||||
The protocol is JSON for the control layer,
|
||||
and a read side of a `pipe()` passed for large data.
|
||||
|
||||
Base JSON protocol:
|
||||
|
||||
request: { method: "MethodName": args: [arguments] }
|
||||
reply: { success: bool, value: JSVAL, pipeid: number, error: string }
|
||||
|
||||
For any non-metadata i.e. payload data from `GetManifest`
|
||||
and `GetBlob` the server will pass back the read half of a `pipe(2)` via FD passing,
|
||||
along with a `pipeid` integer.
|
||||
|
||||
The expected flow looks like this:
|
||||
|
||||
- Initialize
|
||||
And validate the returned protocol version versus
|
||||
what your client supports.
|
||||
- OpenImage docker://quay.io/someorg/example:latest
|
||||
(returns an imageid)
|
||||
- GetManifest imageid (and associated <pipeid>)
|
||||
(Streaming read data from pipe)
|
||||
- FinishPipe <pipeid>
|
||||
- GetBlob imageid sha256:...
|
||||
(Streaming read data from pipe)
|
||||
- FinishPipe <pipeid>
|
||||
- GetBlob imageid sha256:...
|
||||
(Streaming read data from pipe)
|
||||
- FinishPipe <pipeid>
|
||||
- CloseImage imageid
|
||||
|
||||
You may interleave invocations of these methods, e.g. one
|
||||
can also invoke `OpenImage` multiple times, as well as
|
||||
starting multiple GetBlob requests before calling `FinishPipe`
|
||||
on them. The server will stream data into the pipefd
|
||||
until `FinishPipe` is invoked.
|
||||
|
||||
Note that the pipe will not be closed by the server until
|
||||
the client has invoked `FinishPipe`. This is to ensure
|
||||
that the client checks for errors. For example, `GetBlob`
|
||||
performs digest (e.g. sha256) verification and this must
|
||||
be checked after all data has been written.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/image/v5/image"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/pkg/blobinfocache"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// protocolVersion is semantic version of the protocol used by this proxy.
|
||||
// The first version of the protocol has major version 0.2 to signify a
|
||||
// departure from the original code which used HTTP.
|
||||
//
|
||||
// 0.2.1: Initial version
|
||||
// 0.2.2: Added support for fetching image configuration as OCI
|
||||
// 0.2.3: Added GetFullConfig
|
||||
const protocolVersion = "0.2.3"
|
||||
|
||||
// maxMsgSize is the current limit on a packet size.
|
||||
// Note that all non-metadata (i.e. payload data) is sent over a pipe.
|
||||
const maxMsgSize = 32 * 1024
|
||||
|
||||
// maxJSONFloat is ECMA Number.MAX_SAFE_INTEGER
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
|
||||
// We hard error if the input JSON numbers we expect to be
|
||||
// integers are above this.
|
||||
const maxJSONFloat = float64(1<<53 - 1)
|
||||
|
||||
// request is the JSON serialization of a function call
|
||||
type request struct {
|
||||
// Method is the name of the function
|
||||
Method string `json:"method"`
|
||||
// Args is the arguments (parsed inside the function)
|
||||
Args []interface{} `json:"args"`
|
||||
}
|
||||
|
||||
// reply is serialized to JSON as the return value from a function call.
|
||||
type reply struct {
|
||||
// Success is true if and only if the call succeeded.
|
||||
Success bool `json:"success"`
|
||||
// Value is an arbitrary value (or values, as array/map) returned from the call.
|
||||
Value interface{} `json:"value"`
|
||||
// PipeID is an index into open pipes, and should be passed to FinishPipe
|
||||
PipeID uint32 `json:"pipeid"`
|
||||
// Error should be non-empty if Success == false
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// replyBuf is our internal deserialization of reply plus optional fd
|
||||
type replyBuf struct {
|
||||
// value will be converted to a reply Value
|
||||
value interface{}
|
||||
// fd is the read half of a pipe, passed back to the client
|
||||
fd *os.File
|
||||
// pipeid will be provided to the client as PipeID, an index into our open pipes
|
||||
pipeid uint32
|
||||
}
|
||||
|
||||
// activePipe is an open pipe to the client.
|
||||
// It contains an error value
|
||||
type activePipe struct {
|
||||
// w is the write half of the pipe
|
||||
w *os.File
|
||||
// wg is completed when our worker goroutine is done
|
||||
wg sync.WaitGroup
|
||||
// err may be set in our worker goroutine
|
||||
err error
|
||||
}
|
||||
|
||||
// openImage is an opened image reference
|
||||
type openImage struct {
|
||||
// id is an opaque integer handle
|
||||
id uint32
|
||||
src types.ImageSource
|
||||
cachedimg types.Image
|
||||
}
|
||||
|
||||
// proxyHandler is the state associated with our socket.
|
||||
type proxyHandler struct {
|
||||
// lock protects everything else in this structure.
|
||||
lock sync.Mutex
|
||||
// opts is CLI options
|
||||
opts *proxyOptions
|
||||
sysctx *types.SystemContext
|
||||
cache types.BlobInfoCache
|
||||
|
||||
// imageSerial is a counter for open images
|
||||
imageSerial uint32
|
||||
// images holds our opened images
|
||||
images map[uint32]*openImage
|
||||
// activePipes maps from "pipeid" to a pipe + goroutine pair
|
||||
activePipes map[uint32]*activePipe
|
||||
}
|
||||
|
||||
// Initialize performs one-time initialization, and returns the protocol version
|
||||
func (h *proxyHandler) Initialize(args []interface{}) (replyBuf, error) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
var ret replyBuf
|
||||
|
||||
if len(args) != 0 {
|
||||
return ret, fmt.Errorf("invalid request, expecting zero arguments")
|
||||
}
|
||||
|
||||
if h.sysctx != nil {
|
||||
return ret, fmt.Errorf("already initialized")
|
||||
}
|
||||
|
||||
sysctx, err := h.opts.imageOpts.newSystemContext()
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
h.sysctx = sysctx
|
||||
h.cache = blobinfocache.DefaultCache(sysctx)
|
||||
|
||||
r := replyBuf{
|
||||
value: protocolVersion,
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// OpenImage accepts a string image reference i.e. TRANSPORT:REF - like `skopeo copy`.
|
||||
// The return value is an opaque integer handle.
|
||||
func (h *proxyHandler) OpenImage(args []interface{}) (replyBuf, error) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
var ret replyBuf
|
||||
|
||||
if h.sysctx == nil {
|
||||
return ret, fmt.Errorf("client error: must invoke Initialize")
|
||||
}
|
||||
if len(args) != 1 {
|
||||
return ret, fmt.Errorf("invalid request, expecting one argument")
|
||||
}
|
||||
imageref, ok := args[0].(string)
|
||||
if !ok {
|
||||
return ret, fmt.Errorf("expecting string imageref, not %T", args[0])
|
||||
}
|
||||
|
||||
imgRef, err := alltransports.ParseImageName(imageref)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
imgsrc, err := imgRef.NewImageSource(context.Background(), h.sysctx)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
h.imageSerial++
|
||||
openimg := &openImage{
|
||||
id: h.imageSerial,
|
||||
src: imgsrc,
|
||||
}
|
||||
h.images[openimg.id] = openimg
|
||||
ret.value = openimg.id
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (h *proxyHandler) CloseImage(args []interface{}) (replyBuf, error) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
var ret replyBuf
|
||||
|
||||
if h.sysctx == nil {
|
||||
return ret, fmt.Errorf("client error: must invoke Initialize")
|
||||
}
|
||||
if len(args) != 1 {
|
||||
return ret, fmt.Errorf("invalid request, expecting one argument")
|
||||
}
|
||||
imgref, err := h.parseImageFromID(args[0])
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
imgref.src.Close()
|
||||
delete(h.images, imgref.id)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func parseImageID(v interface{}) (uint32, error) {
|
||||
imgidf, ok := v.(float64)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("expecting integer imageid, not %T", v)
|
||||
}
|
||||
return uint32(imgidf), nil
|
||||
}
|
||||
|
||||
// parseUint64 validates that a number fits inside a JavaScript safe integer
|
||||
func parseUint64(v interface{}) (uint64, error) {
|
||||
f, ok := v.(float64)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("expecting numeric, not %T", v)
|
||||
}
|
||||
if f > maxJSONFloat {
|
||||
return 0, fmt.Errorf("out of range integer for numeric %f", f)
|
||||
}
|
||||
return uint64(f), nil
|
||||
}
|
||||
|
||||
func (h *proxyHandler) parseImageFromID(v interface{}) (*openImage, error) {
|
||||
imgid, err := parseImageID(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imgref, ok := h.images[imgid]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no image %v", imgid)
|
||||
}
|
||||
return imgref, nil
|
||||
}
|
||||
|
||||
func (h *proxyHandler) allocPipe() (*os.File, *activePipe, error) {
|
||||
piper, pipew, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
f := activePipe{
|
||||
w: pipew,
|
||||
}
|
||||
h.activePipes[uint32(pipew.Fd())] = &f
|
||||
f.wg.Add(1)
|
||||
return piper, &f, nil
|
||||
}
|
||||
|
||||
// returnBytes generates a return pipe() from a byte array
|
||||
// In the future it might be nicer to return this via memfd_create()
|
||||
func (h *proxyHandler) returnBytes(retval interface{}, buf []byte) (replyBuf, error) {
|
||||
var ret replyBuf
|
||||
piper, f, err := h.allocPipe()
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
// Signal completion when we return
|
||||
defer f.wg.Done()
|
||||
_, err = io.Copy(f.w, bytes.NewReader(buf))
|
||||
if err != nil {
|
||||
f.err = err
|
||||
}
|
||||
}()
|
||||
|
||||
ret.value = retval
|
||||
ret.fd = piper
|
||||
ret.pipeid = uint32(f.w.Fd())
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// cacheTargetManifest is invoked when GetManifest or GetConfig is invoked
|
||||
// the first time for a given image. If the requested image is a manifest
|
||||
// list, this function resolves it to the image matching the calling process'
|
||||
// operating system and architecture.
|
||||
//
|
||||
// TODO: Add GetRawManifest or so that exposes manifest lists
|
||||
func (h *proxyHandler) cacheTargetManifest(img *openImage) error {
|
||||
ctx := context.Background()
|
||||
if img.cachedimg != nil {
|
||||
return nil
|
||||
}
|
||||
unparsedToplevel := image.UnparsedInstance(img.src, nil)
|
||||
mfest, manifestType, err := unparsedToplevel.Manifest(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var target *image.UnparsedImage
|
||||
if manifest.MIMETypeIsMultiImage(manifestType) {
|
||||
manifestList, err := manifest.ListFromBlob(mfest, manifestType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
instanceDigest, err := manifestList.ChooseInstance(h.sysctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target = image.UnparsedInstance(img.src, &instanceDigest)
|
||||
} else {
|
||||
target = unparsedToplevel
|
||||
}
|
||||
cachedimg, err := image.FromUnparsedImage(ctx, h.sysctx, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
img.cachedimg = cachedimg
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetManifest returns a copy of the manifest, converted to OCI format, along with the original digest.
|
||||
// Manifest lists are resolved to the current operating system and architecture.
|
||||
func (h *proxyHandler) GetManifest(args []interface{}) (replyBuf, error) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
var ret replyBuf
|
||||
|
||||
if h.sysctx == nil {
|
||||
return ret, fmt.Errorf("client error: must invoke Initialize")
|
||||
}
|
||||
if len(args) != 1 {
|
||||
return ret, fmt.Errorf("invalid request, expecting one argument")
|
||||
}
|
||||
imgref, err := h.parseImageFromID(args[0])
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
err = h.cacheTargetManifest(imgref)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
img := imgref.cachedimg
|
||||
|
||||
ctx := context.Background()
|
||||
rawManifest, manifestType, err := img.Manifest(ctx)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// We only support OCI and docker2schema2. We know docker2schema2 can be easily+cheaply
|
||||
// converted into OCI, so consumers only need to see OCI.
|
||||
switch manifestType {
|
||||
case imgspecv1.MediaTypeImageManifest, manifest.DockerV2Schema2MediaType:
|
||||
break
|
||||
// Explicitly reject e.g. docker schema 1 type with a "legacy" note
|
||||
case manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema1SignedMediaType:
|
||||
return ret, fmt.Errorf("unsupported legacy manifest MIME type: %s", manifestType)
|
||||
default:
|
||||
return ret, fmt.Errorf("unsupported manifest MIME type: %s", manifestType)
|
||||
}
|
||||
|
||||
// We always return the original digest, as that's what clients need to do pull-by-digest
|
||||
// and in general identify the image.
|
||||
digest, err := manifest.Digest(rawManifest)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
var serialized []byte
|
||||
// But, we convert to OCI format on the wire if it's not already. The idea here is that by reusing the containers/image
|
||||
// stack, clients to this proxy can pretend the world is OCI only, and not need to care about e.g.
|
||||
// docker schema and MIME types.
|
||||
if manifestType != imgspecv1.MediaTypeImageManifest {
|
||||
manifestUpdates := types.ManifestUpdateOptions{ManifestMIMEType: imgspecv1.MediaTypeImageManifest}
|
||||
ociImage, err := img.UpdatedImage(ctx, manifestUpdates)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
ociSerialized, _, err := ociImage.Manifest(ctx)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
serialized = ociSerialized
|
||||
} else {
|
||||
serialized = rawManifest
|
||||
}
|
||||
return h.returnBytes(digest, serialized)
|
||||
}
|
||||
|
||||
// GetFullConfig returns a copy of the image configuration, converted to OCI format.
|
||||
// https://github.com/opencontainers/image-spec/blob/main/config.md
|
||||
func (h *proxyHandler) GetFullConfig(args []interface{}) (replyBuf, error) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
var ret replyBuf
|
||||
|
||||
if h.sysctx == nil {
|
||||
return ret, fmt.Errorf("client error: must invoke Initialize")
|
||||
}
|
||||
if len(args) != 1 {
|
||||
return ret, fmt.Errorf("invalid request, expecting: [imgid]")
|
||||
}
|
||||
imgref, err := h.parseImageFromID(args[0])
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
err = h.cacheTargetManifest(imgref)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
img := imgref.cachedimg
|
||||
|
||||
ctx := context.TODO()
|
||||
config, err := img.OCIConfig(ctx)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
serialized, err := json.Marshal(&config)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
return h.returnBytes(nil, serialized)
|
||||
}
|
||||
|
||||
// GetConfig returns a copy of the container runtime configuration, converted to OCI format.
|
||||
// Note that due to a historical mistake, this returns not the full image configuration,
|
||||
// but just the container runtime configuration. You should use GetFullConfig instead.
|
||||
func (h *proxyHandler) GetConfig(args []interface{}) (replyBuf, error) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
var ret replyBuf
|
||||
|
||||
if h.sysctx == nil {
|
||||
return ret, fmt.Errorf("client error: must invoke Initialize")
|
||||
}
|
||||
if len(args) != 1 {
|
||||
return ret, fmt.Errorf("invalid request, expecting: [imgid]")
|
||||
}
|
||||
imgref, err := h.parseImageFromID(args[0])
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
err = h.cacheTargetManifest(imgref)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
img := imgref.cachedimg
|
||||
|
||||
ctx := context.TODO()
|
||||
config, err := img.OCIConfig(ctx)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
serialized, err := json.Marshal(&config.Config)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
return h.returnBytes(nil, serialized)
|
||||
}
|
||||
|
||||
// GetBlob fetches a blob, performing digest verification.
|
||||
func (h *proxyHandler) GetBlob(args []interface{}) (replyBuf, error) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
var ret replyBuf
|
||||
|
||||
if h.sysctx == nil {
|
||||
return ret, fmt.Errorf("client error: must invoke Initialize")
|
||||
}
|
||||
if len(args) != 3 {
|
||||
return ret, fmt.Errorf("found %d args, expecting (imgid, digest, size)", len(args))
|
||||
}
|
||||
imgref, err := h.parseImageFromID(args[0])
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
digestStr, ok := args[1].(string)
|
||||
if !ok {
|
||||
return ret, fmt.Errorf("expecting string blobid")
|
||||
}
|
||||
size, err := parseUint64(args[2])
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
d, err := digest.Parse(digestStr)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
blobr, blobSize, err := imgref.src.GetBlob(ctx, types.BlobInfo{Digest: d, Size: int64(size)}, h.cache)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
piper, f, err := h.allocPipe()
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
go func() {
|
||||
// Signal completion when we return
|
||||
defer f.wg.Done()
|
||||
verifier := d.Verifier()
|
||||
tr := io.TeeReader(blobr, verifier)
|
||||
n, err := io.Copy(f.w, tr)
|
||||
if err != nil {
|
||||
f.err = err
|
||||
return
|
||||
}
|
||||
if n != int64(size) {
|
||||
f.err = fmt.Errorf("expected %d bytes in blob, got %d", size, n)
|
||||
}
|
||||
if !verifier.Verified() {
|
||||
f.err = fmt.Errorf("corrupted blob, expecting %s", d.String())
|
||||
}
|
||||
}()
|
||||
|
||||
ret.value = blobSize
|
||||
ret.fd = piper
|
||||
ret.pipeid = uint32(f.w.Fd())
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// FinishPipe waits for the worker goroutine to finish, and closes the write side of the pipe.
|
||||
func (h *proxyHandler) FinishPipe(args []interface{}) (replyBuf, error) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
var ret replyBuf
|
||||
|
||||
pipeidv, err := parseUint64(args[0])
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
pipeid := uint32(pipeidv)
|
||||
|
||||
f, ok := h.activePipes[pipeid]
|
||||
if !ok {
|
||||
return ret, fmt.Errorf("finishpipe: no active pipe %d", pipeid)
|
||||
}
|
||||
|
||||
// Wait for the goroutine to complete
|
||||
f.wg.Wait()
|
||||
// And only now do we close the write half; this forces the client to call this API
|
||||
f.w.Close()
|
||||
// Propagate any errors from the goroutine worker
|
||||
err = f.err
|
||||
delete(h.activePipes, pipeid)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// send writes a reply buffer to the socket
|
||||
func (buf replyBuf) send(conn *net.UnixConn, err error) error {
|
||||
replyToSerialize := reply{
|
||||
Success: err == nil,
|
||||
Value: buf.value,
|
||||
PipeID: buf.pipeid,
|
||||
}
|
||||
if err != nil {
|
||||
replyToSerialize.Error = err.Error()
|
||||
}
|
||||
serializedReply, err := json.Marshal(&replyToSerialize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// We took ownership of the FD - close it when we're done.
|
||||
defer func() {
|
||||
if buf.fd != nil {
|
||||
buf.fd.Close()
|
||||
}
|
||||
}()
|
||||
// Copy the FD number to the socket ancillary buffer
|
||||
fds := make([]int, 0)
|
||||
if buf.fd != nil {
|
||||
fds = append(fds, int(buf.fd.Fd()))
|
||||
}
|
||||
oob := syscall.UnixRights(fds...)
|
||||
n, oobn, err := conn.WriteMsgUnix(serializedReply, oob, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Validate that we sent the full packet
|
||||
if n != len(serializedReply) || oobn != len(oob) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type proxyOptions struct {
|
||||
global *globalOptions
|
||||
imageOpts *imageOptions
|
||||
sockFd int
|
||||
}
|
||||
|
||||
func proxyCmd(global *globalOptions) *cobra.Command {
|
||||
sharedFlags, sharedOpts := sharedImageFlags()
|
||||
imageFlags, imageOpts := imageFlags(global, sharedOpts, nil, "", "")
|
||||
opts := proxyOptions{global: global, imageOpts: imageOpts}
|
||||
cmd := &cobra.Command{
|
||||
Use: "experimental-image-proxy [command options] IMAGE",
|
||||
Short: "Interactive proxy for fetching container images (EXPERIMENTAL)",
|
||||
Long: `Run skopeo as a proxy, supporting HTTP requests to fetch manifests and blobs.`,
|
||||
RunE: commandAction(opts.run),
|
||||
Args: cobra.ExactArgs(0),
|
||||
// Not stabilized yet
|
||||
Hidden: true,
|
||||
Example: `skopeo experimental-image-proxy --sockfd 3`,
|
||||
}
|
||||
adjustUsage(cmd)
|
||||
flags := cmd.Flags()
|
||||
flags.AddFlagSet(&sharedFlags)
|
||||
flags.AddFlagSet(&imageFlags)
|
||||
flags.IntVar(&opts.sockFd, "sockfd", 0, "Serve on opened socket pair (default 0/stdin)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// processRequest dispatches a remote request.
|
||||
// replyBuf is the result of the invocation.
|
||||
// terminate should be true if processing of requests should halt.
|
||||
func (h *proxyHandler) processRequest(req request) (rb replyBuf, terminate bool, err error) {
|
||||
// Dispatch on the method
|
||||
switch req.Method {
|
||||
case "Initialize":
|
||||
rb, err = h.Initialize(req.Args)
|
||||
case "OpenImage":
|
||||
rb, err = h.OpenImage(req.Args)
|
||||
case "CloseImage":
|
||||
rb, err = h.CloseImage(req.Args)
|
||||
case "GetManifest":
|
||||
rb, err = h.GetManifest(req.Args)
|
||||
case "GetConfig":
|
||||
rb, err = h.GetConfig(req.Args)
|
||||
case "GetFullConfig":
|
||||
rb, err = h.GetFullConfig(req.Args)
|
||||
case "GetBlob":
|
||||
rb, err = h.GetBlob(req.Args)
|
||||
case "FinishPipe":
|
||||
rb, err = h.FinishPipe(req.Args)
|
||||
case "Shutdown":
|
||||
terminate = true
|
||||
default:
|
||||
err = fmt.Errorf("unknown method: %s", req.Method)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Implementation of podman experimental-image-proxy
|
||||
func (opts *proxyOptions) run(args []string, stdout io.Writer) error {
|
||||
handler := &proxyHandler{
|
||||
opts: opts,
|
||||
images: make(map[uint32]*openImage),
|
||||
activePipes: make(map[uint32]*activePipe),
|
||||
}
|
||||
|
||||
// Convert the socket FD passed by client into a net.FileConn
|
||||
fd := os.NewFile(uintptr(opts.sockFd), "sock")
|
||||
fconn, err := net.FileConn(fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn := fconn.(*net.UnixConn)
|
||||
|
||||
// Allocate a buffer to copy the packet into
|
||||
buf := make([]byte, maxMsgSize)
|
||||
for {
|
||||
n, _, err := conn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("reading socket: %v", err)
|
||||
}
|
||||
// Parse the request JSON
|
||||
readbuf := buf[0:n]
|
||||
var req request
|
||||
if err := json.Unmarshal(readbuf, &req); err != nil {
|
||||
rb := replyBuf{}
|
||||
rb.send(conn, fmt.Errorf("invalid request: %v", err))
|
||||
}
|
||||
|
||||
rb, terminate, err := handler.processRequest(req)
|
||||
if terminate {
|
||||
return nil
|
||||
}
|
||||
rb.send(conn, err)
|
||||
}
|
||||
}
|
||||
30
cmd/skopeo/proxy_windows.go
Normal file
30
cmd/skopeo/proxy_windows.go
Normal file
@@ -0,0 +1,30 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type proxyOptions struct {
|
||||
global *globalOptions
|
||||
}
|
||||
|
||||
func proxyCmd(global *globalOptions) *cobra.Command {
|
||||
opts := proxyOptions{global: global}
|
||||
cmd := &cobra.Command{
|
||||
RunE: commandAction(opts.run),
|
||||
Args: cobra.ExactArgs(0),
|
||||
// Not stabilized yet
|
||||
Hidden: true,
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (opts *proxyOptions) run(args []string, stdout io.Writer) error {
|
||||
return fmt.Errorf("This command is not supported on Windows")
|
||||
}
|
||||
@@ -7,12 +7,14 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/containers/image/v5/pkg/cli"
|
||||
"github.com/containers/image/v5/signature"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type standaloneSignOptions struct {
|
||||
output string // Output file path
|
||||
output string // Output file path
|
||||
passphraseFile string // Path pointing to a passphrase file when signing
|
||||
}
|
||||
|
||||
func standaloneSignCmd() *cobra.Command {
|
||||
@@ -25,6 +27,7 @@ func standaloneSignCmd() *cobra.Command {
|
||||
adjustUsage(cmd)
|
||||
flags := cmd.Flags()
|
||||
flags.StringVarP(&opts.output, "output", "o", "", "output the signature to `SIGNATURE`")
|
||||
flags.StringVarP(&opts.passphraseFile, "passphrase-file", "", "", "file that contains a passphrase for the --sign-by key")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -46,7 +49,13 @@ func (opts *standaloneSignOptions) run(args []string, stdout io.Writer) error {
|
||||
return fmt.Errorf("Error initializing GPG: %v", err)
|
||||
}
|
||||
defer mech.Close()
|
||||
signature, err := signature.SignDockerManifest(manifest, dockerReference, mech, fingerprint)
|
||||
|
||||
passphrase, err := cli.ReadPassphraseFile(opts.passphraseFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signature, err := signature.SignDockerManifestWithOptions(manifest, dockerReference, mech, fingerprint, &signature.SignOptions{Passphrase: passphrase})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating signature: %v", err)
|
||||
}
|
||||
|
||||
@@ -11,11 +11,13 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
commonFlag "github.com/containers/common/pkg/flag"
|
||||
"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/pkg/cli"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
@@ -32,14 +34,16 @@ type syncOptions struct {
|
||||
srcImage *imageOptions // Source image options
|
||||
destImage *imageDestOptions // Destination image options
|
||||
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
|
||||
keepGoing bool // Whether or not to abort the sync if there are any errors during syncing the images
|
||||
removeSignatures bool // Do not copy signatures from the source image
|
||||
signByFingerprint string // Sign the image using a GPG key with the specified fingerprint
|
||||
signPassphraseFile string // Path pointing to a passphrase file when signing
|
||||
format commonFlag.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
|
||||
preserveDigests bool // Preserve digests during sync
|
||||
keepGoing bool // Whether or not to abort the sync if there are any errors during syncing the images
|
||||
}
|
||||
|
||||
// repoDescriptor contains information of a single repository used as a sync source.
|
||||
@@ -100,11 +104,13 @@ See skopeo-sync(1) for details.
|
||||
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, with fallbacks)`)
|
||||
flags.StringVar(&opts.signPassphraseFile, "sign-passphrase-file", "", "File that contains a passphrase for the --sign-by key")
|
||||
flags.VarP(commonFlag.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, with fallbacks)`)
|
||||
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.BoolVar(&opts.preserveDigests, "preserve-digests", false, "Preserve digests of images and lists")
|
||||
flags.BoolVarP(&opts.keepGoing, "keep-going", "", false, "Do not abort the sync if any image copy fails")
|
||||
flags.AddFlagSet(&sharedFlags)
|
||||
flags.AddFlagSet(&deprecatedTLSVerifyFlags)
|
||||
@@ -545,8 +551,8 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) error {
|
||||
}
|
||||
|
||||
var manifestType string
|
||||
if opts.format.present {
|
||||
manifestType, err = parseManifestFormat(opts.format.value)
|
||||
if opts.format.Present() {
|
||||
manifestType, err = parseManifestFormat(opts.format.Value())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -570,12 +576,18 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
passphrase, err := cli.ReadPassphraseFile(opts.signPassphraseFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options := copy.Options{
|
||||
RemoveSignatures: opts.removeSignatures,
|
||||
SignBy: opts.signByFingerprint,
|
||||
SignPassphrase: passphrase,
|
||||
ReportWriter: os.Stdout,
|
||||
DestinationCtx: destinationCtx,
|
||||
ImageListSelection: imageListSelection,
|
||||
PreserveDigests: opts.preserveDigests,
|
||||
OptimizeDestinationImageAlreadyExists: true,
|
||||
ForceManifestMIMEType: manifestType,
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
commonFlag "github.com/containers/common/pkg/flag"
|
||||
"github.com/containers/common/pkg/retry"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/pkg/compression"
|
||||
@@ -45,7 +46,7 @@ func commandAction(handler func(args []string, stdout io.Writer) error) func(cmd
|
||||
// whether or not the value actually ends up being used.
|
||||
// DO NOT ADD ANY NEW USES OF THIS; just call dockerImageFlags with an appropriate, possibly empty, flagPrefix.
|
||||
type deprecatedTLSVerifyOption struct {
|
||||
tlsVerify optionalBool // FIXME FIXME: Warn if this is used, or even if it is ignored.
|
||||
tlsVerify commonFlag.OptionalBool // FIXME FIXME: Warn if this is used, or even if it is ignored.
|
||||
}
|
||||
|
||||
// warnIfUsed warns if tlsVerify was set by the user, and suggests alternatives (which should
|
||||
@@ -53,7 +54,7 @@ type deprecatedTLSVerifyOption struct {
|
||||
// Every user should call this as part of handling the CLI, whether or not the value actually
|
||||
// ends up being used.
|
||||
func (opts *deprecatedTLSVerifyOption) warnIfUsed(alternatives []string) {
|
||||
if opts.tlsVerify.present {
|
||||
if opts.tlsVerify.Present() {
|
||||
logrus.Warnf("'--tls-verify' is deprecated, instead use: %s", strings.Join(alternatives, ", "))
|
||||
}
|
||||
}
|
||||
@@ -63,7 +64,7 @@ func (opts *deprecatedTLSVerifyOption) warnIfUsed(alternatives []string) {
|
||||
func deprecatedTLSVerifyFlags() (pflag.FlagSet, *deprecatedTLSVerifyOption) {
|
||||
opts := deprecatedTLSVerifyOption{}
|
||||
fs := pflag.FlagSet{}
|
||||
flag := optionalBoolFlag(&fs, &opts.tlsVerify, "tls-verify", "require HTTPS and verify certificates when accessing the container registry")
|
||||
flag := commonFlag.OptionalBoolFlag(&fs, &opts.tlsVerify, "tls-verify", "require HTTPS and verify certificates when accessing the container registry")
|
||||
flag.Hidden = true
|
||||
return fs, &opts
|
||||
}
|
||||
@@ -89,11 +90,13 @@ type dockerImageOptions struct {
|
||||
global *globalOptions // May be shared across several imageOptions instances.
|
||||
shared *sharedImageOptions // May be shared across several imageOptions instances.
|
||||
deprecatedTLSVerify *deprecatedTLSVerifyOption // May be shared across several imageOptions instances, or nil.
|
||||
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
|
||||
authFilePath commonFlag.OptionalString // Path to a */containers/auth.json (prefixed version to override shared image option).
|
||||
credsOption commonFlag.OptionalString // username[:password] for accessing a registry
|
||||
userName commonFlag.OptionalString // username for accessing a registry
|
||||
password commonFlag.OptionalString // password for accessing a registry
|
||||
registryToken commonFlag.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:)
|
||||
tlsVerify commonFlag.OptionalBool // Require HTTPS and verify certificates (for docker: and docker-daemon:)
|
||||
noCreds bool // Access the registry anonymously
|
||||
}
|
||||
|
||||
@@ -119,18 +122,20 @@ func dockerImageFlags(global *globalOptions, shared *sharedImageOptions, depreca
|
||||
fs := pflag.FlagSet{}
|
||||
if flagPrefix != "" {
|
||||
// the non-prefixed flag is handled by a shared flag.
|
||||
fs.Var(newOptionalStringValue(&flags.authFilePath), flagPrefix+"authfile", "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json")
|
||||
fs.Var(commonFlag.NewOptionalStringValue(&flags.authFilePath), flagPrefix+"authfile", "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json")
|
||||
}
|
||||
fs.Var(newOptionalStringValue(&flags.credsOption), flagPrefix+"creds", "Use `USERNAME[:PASSWORD]` for accessing the registry")
|
||||
fs.Var(commonFlag.NewOptionalStringValue(&flags.credsOption), flagPrefix+"creds", "Use `USERNAME[:PASSWORD]` for accessing the registry")
|
||||
fs.Var(commonFlag.NewOptionalStringValue(&flags.userName), flagPrefix+"username", "Username for accessing the registry")
|
||||
fs.Var(commonFlag.NewOptionalStringValue(&flags.password), flagPrefix+"password", "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 := fs.VarPF(commonFlag.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.Var(commonFlag.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")
|
||||
commonFlag.OptionalBoolFlag(&fs, &flags.tlsVerify, flagPrefix+"tls-verify", "require HTTPS and verify certificates when talking to the container registry or daemon")
|
||||
fs.BoolVar(&flags.noCreds, flagPrefix+"no-creds", false, "Access the registry anonymously")
|
||||
return fs, &flags
|
||||
}
|
||||
@@ -164,31 +169,49 @@ func (opts *imageOptions) newSystemContext() (*types.SystemContext, error) {
|
||||
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.dockerImageOptions.authFilePath.Present() {
|
||||
ctx.AuthFilePath = opts.dockerImageOptions.authFilePath.Value()
|
||||
}
|
||||
if opts.deprecatedTLSVerify != nil && opts.deprecatedTLSVerify.tlsVerify.present {
|
||||
if opts.deprecatedTLSVerify != nil && opts.deprecatedTLSVerify.tlsVerify.Present() {
|
||||
// If both this deprecated option and a non-deprecated option is present, we use the latter value.
|
||||
ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.deprecatedTLSVerify.tlsVerify.value)
|
||||
ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.deprecatedTLSVerify.tlsVerify.Value())
|
||||
}
|
||||
if opts.tlsVerify.present {
|
||||
ctx.DockerDaemonInsecureSkipTLSVerify = !opts.tlsVerify.value
|
||||
if opts.tlsVerify.Present() {
|
||||
ctx.DockerDaemonInsecureSkipTLSVerify = !opts.tlsVerify.Value()
|
||||
}
|
||||
if opts.tlsVerify.present {
|
||||
ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.tlsVerify.value)
|
||||
if opts.tlsVerify.Present() {
|
||||
ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.tlsVerify.Value())
|
||||
}
|
||||
if opts.credsOption.present && opts.noCreds {
|
||||
if opts.credsOption.Present() && opts.noCreds {
|
||||
return nil, errors.New("creds and no-creds cannot be specified at the same time")
|
||||
}
|
||||
if opts.credsOption.present {
|
||||
if opts.userName.Present() && opts.noCreds {
|
||||
return nil, errors.New("username and no-creds cannot be specified at the same time")
|
||||
}
|
||||
if opts.credsOption.Present() && opts.userName.Present() {
|
||||
return nil, errors.New("creds and username cannot be specified at the same time")
|
||||
}
|
||||
// if any of username or password is present, then both are expected to be present
|
||||
if opts.userName.Present() != opts.password.Present() {
|
||||
if opts.userName.Present() {
|
||||
return nil, errors.New("password must be specified when username is specified")
|
||||
}
|
||||
return nil, errors.New("username must be specified when password is specified")
|
||||
}
|
||||
if opts.credsOption.Present() {
|
||||
var err error
|
||||
ctx.DockerAuthConfig, err = getDockerAuth(opts.credsOption.value)
|
||||
ctx.DockerAuthConfig, err = getDockerAuth(opts.credsOption.Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if opts.userName.Present() {
|
||||
ctx.DockerAuthConfig = &types.DockerAuthConfig{
|
||||
Username: opts.userName.Value(),
|
||||
Password: opts.password.Value(),
|
||||
}
|
||||
}
|
||||
if opts.registryToken.present {
|
||||
ctx.DockerBearerRegistryToken = opts.registryToken.value
|
||||
if opts.registryToken.Present() {
|
||||
ctx.DockerBearerRegistryToken = opts.registryToken.Value()
|
||||
}
|
||||
if opts.noCreds {
|
||||
ctx.DockerAuthConfig = &types.DockerAuthConfig{}
|
||||
@@ -200,11 +223,12 @@ func (opts *imageOptions) newSystemContext() (*types.SystemContext, error) {
|
||||
// imageDestOptions is a superset of imageOptions specialized for image destinations.
|
||||
type imageDestOptions struct {
|
||||
*imageOptions
|
||||
dirForceCompression bool // Compress layers when saving to the dir: transport
|
||||
dirForceDecompression bool // Decompress layers when saving to the dir: transport
|
||||
ociAcceptUncompressedLayers bool // Whether to accept uncompressed layers in the oci: transport
|
||||
compressionFormat string // Format to use for the compression
|
||||
compressionLevel optionalInt // Level to use for the compression
|
||||
dirForceCompression bool // Compress layers when saving to the dir: transport
|
||||
dirForceDecompression bool // Decompress layers when saving to the dir: transport
|
||||
ociAcceptUncompressedLayers bool // Whether to accept uncompressed layers in the oci: transport
|
||||
compressionFormat string // Format to use for the compression
|
||||
compressionLevel commonFlag.OptionalInt // Level to use for the compression
|
||||
precomputeDigests bool // Precompute digests to dedup layers when saving to the docker: transport
|
||||
}
|
||||
|
||||
// imageDestFlags prepares a collection of CLI flags writing into imageDestOptions, and the managed imageDestOptions structure.
|
||||
@@ -217,7 +241,8 @@ func imageDestFlags(global *globalOptions, shared *sharedImageOptions, deprecate
|
||||
fs.BoolVar(&opts.dirForceDecompression, flagPrefix+"decompress", false, "Decompress 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")
|
||||
fs.Var(commonFlag.NewOptionalIntValue(&opts.compressionLevel), flagPrefix+"compress-level", "`LEVEL` to use for the compression")
|
||||
fs.BoolVar(&opts.precomputeDigests, flagPrefix+"precompute-digests", false, "Precompute digests to prevent uploading layers already on the registry using the 'docker' transport.")
|
||||
return fs, &opts
|
||||
}
|
||||
|
||||
@@ -239,9 +264,11 @@ func (opts *imageDestOptions) newSystemContext() (*types.SystemContext, error) {
|
||||
}
|
||||
ctx.CompressionFormat = &cf
|
||||
}
|
||||
if opts.compressionLevel.present {
|
||||
ctx.CompressionLevel = &opts.compressionLevel.value
|
||||
if opts.compressionLevel.Present() {
|
||||
value := opts.compressionLevel.Value()
|
||||
ctx.CompressionLevel = &value
|
||||
}
|
||||
ctx.DockerRegistryPushPrecomputeDigests = opts.precomputeDigests
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
|
||||
@@ -167,26 +167,28 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) {
|
||||
"--dest-tls-verify=false",
|
||||
"--dest-creds", "creds-user:creds-password",
|
||||
"--dest-registry-token", "faketoken",
|
||||
"--dest-precompute-digests=true",
|
||||
})
|
||||
res, err = opts.newSystemContext()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &types.SystemContext{
|
||||
RegistriesDirPath: "/srv/registries.d",
|
||||
AuthFilePath: "/srv/authfile",
|
||||
ArchitectureChoice: "overridden-arch",
|
||||
OSChoice: "overridden-os",
|
||||
VariantChoice: "overridden-variant",
|
||||
OCISharedBlobDirPath: "/srv/shared-blob-dir",
|
||||
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",
|
||||
RegistriesDirPath: "/srv/registries.d",
|
||||
AuthFilePath: "/srv/authfile",
|
||||
ArchitectureChoice: "overridden-arch",
|
||||
OSChoice: "overridden-os",
|
||||
VariantChoice: "overridden-variant",
|
||||
OCISharedBlobDirPath: "/srv/shared-blob-dir",
|
||||
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",
|
||||
DockerRegistryPushPrecomputeDigests: true,
|
||||
}, res)
|
||||
|
||||
// Global/per-command tlsVerify behavior is tested in TestTLSVerifyFlags.
|
||||
@@ -197,6 +199,54 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// TestImageOptionsUsernamePassword verifies that using the username and password
|
||||
// options works as expected
|
||||
func TestImageOptionsUsernamePassword(t *testing.T) {
|
||||
for _, command := range []struct {
|
||||
commandArgs []string
|
||||
expectedAuthConfig *types.DockerAuthConfig // data to expect, or nil if an error is expected
|
||||
}{
|
||||
// Set only username/password (without --creds), expected to pass
|
||||
{
|
||||
commandArgs: []string{"--dest-username", "foo", "--dest-password", "bar"},
|
||||
expectedAuthConfig: &types.DockerAuthConfig{Username: "foo", Password: "bar"},
|
||||
},
|
||||
// no username but set password, expect error
|
||||
{
|
||||
commandArgs: []string{"--dest-password", "foo"},
|
||||
expectedAuthConfig: nil,
|
||||
},
|
||||
// set username but no password. expected to fail (we currently don't allow a user without password)
|
||||
{
|
||||
commandArgs: []string{"--dest-username", "bar"},
|
||||
expectedAuthConfig: nil,
|
||||
},
|
||||
// set username with --creds, expected to fail
|
||||
{
|
||||
commandArgs: []string{"--dest-username", "bar", "--dest-creds", "hello:world", "--dest-password", "foo"},
|
||||
expectedAuthConfig: nil,
|
||||
},
|
||||
// set username with --no-creds, expected to fail
|
||||
{
|
||||
commandArgs: []string{"--dest-username", "bar", "--dest-no-creds", "--dest-password", "foo"},
|
||||
expectedAuthConfig: nil,
|
||||
},
|
||||
} {
|
||||
opts := fakeImageDestOptions(t, "dest-", true, []string{}, command.commandArgs)
|
||||
// parse the command options
|
||||
res, err := opts.newSystemContext()
|
||||
if command.expectedAuthConfig == nil {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &types.SystemContext{
|
||||
DockerRegistryUserAgent: defaultUserAgent,
|
||||
DockerAuthConfig: command.expectedAuthConfig,
|
||||
}, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSVerifyFlags(t *testing.T) {
|
||||
type systemContextOpts interface { // Either *imageOptions or *imageDestOptions
|
||||
newSystemContext() (*types.SystemContext, error)
|
||||
|
||||
@@ -40,7 +40,9 @@ _skopeo_copy() {
|
||||
--src-authfile
|
||||
--dest-authfile
|
||||
--format -f
|
||||
--multi-arch
|
||||
--sign-by
|
||||
--sign-passphrase-file
|
||||
--src-creds --screds
|
||||
--src-cert-dir
|
||||
--src-tls-verify
|
||||
@@ -51,6 +53,10 @@ _skopeo_copy() {
|
||||
--dest-daemon-host
|
||||
--src-registry-token
|
||||
--dest-registry-token
|
||||
--src-username
|
||||
--src-password
|
||||
--dest-username
|
||||
--dest-password
|
||||
"
|
||||
|
||||
local boolean_options="
|
||||
@@ -61,6 +67,8 @@ _skopeo_copy() {
|
||||
--src-no-creds
|
||||
--dest-no-creds
|
||||
--dest-oci-accept-uncompressed-layers
|
||||
--dest-precompute-digests
|
||||
--preserve-digests
|
||||
"
|
||||
|
||||
local transports
|
||||
@@ -82,11 +90,16 @@ _skopeo_sync() {
|
||||
--format
|
||||
--retry-times
|
||||
--sign-by
|
||||
--sign-passphrase-file
|
||||
--src
|
||||
--src-authfile
|
||||
--src-cert-dir
|
||||
--src-creds
|
||||
--src-registry-token
|
||||
--src-username
|
||||
--src-password
|
||||
--dest-username
|
||||
--dest-password
|
||||
"
|
||||
|
||||
local boolean_options="
|
||||
@@ -98,6 +111,7 @@ _skopeo_sync() {
|
||||
--src-no-creds
|
||||
--src-tls-verify
|
||||
--keep-going
|
||||
--preserve-digests
|
||||
"
|
||||
|
||||
local transports
|
||||
@@ -116,12 +130,15 @@ _skopeo_inspect() {
|
||||
--format
|
||||
--retry-times
|
||||
--registry-token
|
||||
--username
|
||||
--password
|
||||
"
|
||||
local boolean_options="
|
||||
--config
|
||||
--raw
|
||||
--tls-verify
|
||||
--no-creds
|
||||
--no-tags -n
|
||||
"
|
||||
|
||||
local transports
|
||||
@@ -135,6 +152,7 @@ _skopeo_inspect() {
|
||||
_skopeo_standalone_sign() {
|
||||
local options_with_args="
|
||||
-o --output
|
||||
--passphrase-file
|
||||
"
|
||||
local boolean_options="
|
||||
"
|
||||
@@ -163,6 +181,8 @@ _skopeo_delete() {
|
||||
--creds
|
||||
--cert-dir
|
||||
--registry-token
|
||||
--username
|
||||
--password
|
||||
"
|
||||
local boolean_options="
|
||||
--tls-verify
|
||||
@@ -183,6 +203,8 @@ _skopeo_layers() {
|
||||
--creds
|
||||
--cert-dir
|
||||
--registry-token
|
||||
--username
|
||||
--password
|
||||
"
|
||||
local boolean_options="
|
||||
--tls-verify
|
||||
@@ -197,6 +219,8 @@ _skopeo_list_repository_tags() {
|
||||
--creds
|
||||
--cert-dir
|
||||
--registry-token
|
||||
--username
|
||||
--password
|
||||
"
|
||||
|
||||
local boolean_options="
|
||||
|
||||
@@ -58,13 +58,16 @@ _run_setup() {
|
||||
return
|
||||
fi
|
||||
|
||||
# This is required as part of the standard Fedora GCE VM setup
|
||||
growpart /dev/sda 1
|
||||
resize2fs /dev/sda1
|
||||
|
||||
# VM's come with the distro. skopeo package pre-installed
|
||||
dnf erase -y skopeo
|
||||
|
||||
# Required for testing the SIF transport
|
||||
dnf install -y fakeroot squashfs-tools
|
||||
|
||||
msg "Removing systemd-resolved from nsswitch.conf"
|
||||
# /etc/resolv.conf is already set to bypass systemd-resolvd
|
||||
sed -i -r -e 's/^(hosts.+)resolve.+dns/\1dns/' /etc/nsswitch.conf
|
||||
|
||||
# A slew of compiled binaries are pre-built and distributed
|
||||
# within the CI/Dev container image, but we want to run
|
||||
# things directly on the host VM. Fortunately they're all
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# This image can be used to create a secured container
|
||||
# that runs safely with privileges within the container.
|
||||
#
|
||||
FROM registry.fedoraproject.org/fedora:33
|
||||
FROM registry.fedoraproject.org/fedora:latest
|
||||
|
||||
# Don't include container-selinux and remove
|
||||
# directories used by yum that are just taking
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# This image can be used to create a secured container
|
||||
# that runs safely with privileges within the container.
|
||||
#
|
||||
FROM registry.fedoraproject.org/fedora:33
|
||||
FROM registry.fedoraproject.org/fedora:latest
|
||||
|
||||
# Don't include container-selinux and remove
|
||||
# directories used by yum that are just taking
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# This image can be used to create a secured container
|
||||
# that runs safely with privileges within the container.
|
||||
#
|
||||
FROM registry.fedoraproject.org/fedora:33
|
||||
FROM registry.fedoraproject.org/fedora:latest
|
||||
|
||||
# Don't include container-selinux and remove
|
||||
# directories used by yum that are just taking
|
||||
|
||||
@@ -54,6 +54,10 @@ Directory to use to share blobs across OCI repositories.
|
||||
|
||||
After copying the image, write the digest of the resulting image to the file.
|
||||
|
||||
**--preserve-digests**
|
||||
|
||||
Preserve the digests during copying. Fail if the digest cannot be preserved.
|
||||
|
||||
**--encrypt-layer** _ints_
|
||||
|
||||
*Experimental* the 0-indexed layer indices, with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer)
|
||||
@@ -66,6 +70,17 @@ MANIFEST TYPE (oci, v2s1, or v2s2) to use in the destination (default is manifes
|
||||
|
||||
Print usage statement
|
||||
|
||||
**--multi-arch**
|
||||
|
||||
Control what is copied if _source-image_ refers to a multi-architecture image. Default is system.
|
||||
|
||||
Options:
|
||||
- system: Copy only the image that matches the system architecture
|
||||
- all: Copy the full multi-architecture image
|
||||
- index-only: Copy only the index
|
||||
|
||||
The index-only option usually fails unless the referenced per-architecture images are already present in the destination, or the target registry supports sparse indexes.
|
||||
|
||||
**--quiet**, **-q**
|
||||
|
||||
Suppress output information when copying images.
|
||||
@@ -78,6 +93,10 @@ Do not copy signatures, if any, from _source-image_. Necessary when copying a si
|
||||
|
||||
Add a signature using that key ID for an image name corresponding to _destination-image_
|
||||
|
||||
**--sign-passphrase-file**=_path_
|
||||
|
||||
The passphare to use when signing with the key ID from `--sign-by`. Only the first line will be read. A passphrase stored in a file is of questionable security if other users can read this file. Do not use this option if at all avoidable.
|
||||
|
||||
**--src-shared-blob-dir** _directory_
|
||||
|
||||
Directory to use to share blobs across OCI repositories.
|
||||
@@ -94,15 +113,15 @@ Key to be used for decryption of images. Key can point to keys and/or certificat
|
||||
|
||||
Credentials for accessing the source registry.
|
||||
|
||||
**--dest-compress** _bool-value_
|
||||
**--dest-compress**
|
||||
|
||||
Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source).
|
||||
|
||||
**--dest-decompress** _bool-value_
|
||||
**--dest-decompress**
|
||||
|
||||
Decompress 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_
|
||||
**--dest-oci-accept-uncompressed-layers**
|
||||
|
||||
Allow uncompressed image layers when saving to an OCI image using the 'oci' transport. (default is to compress things that aren't compressed).
|
||||
|
||||
@@ -114,11 +133,11 @@ Credentials for accessing the destination registry.
|
||||
|
||||
Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the source registry or daemon.
|
||||
|
||||
**--src-no-creds** _bool-value_
|
||||
**--src-no-creds**
|
||||
|
||||
Access the registry anonymously.
|
||||
|
||||
**--src-tls-verify** _bool-value_
|
||||
**--src-tls-verify**=_bool_
|
||||
|
||||
Require HTTPS and verify certificates when talking to container source registry or daemon. Default to source registry setting.
|
||||
|
||||
@@ -126,11 +145,11 @@ Require HTTPS and verify certificates when talking to container source registry
|
||||
|
||||
Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the destination registry or daemon.
|
||||
|
||||
**--dest-no-creds** _bool-value_
|
||||
**--dest-no-creds**
|
||||
|
||||
Access the registry anonymously.
|
||||
|
||||
**--dest-tls-verify** _bool-value_
|
||||
**--dest-tls-verify**=_bool_
|
||||
|
||||
Require HTTPS and verify certificates when talking to container destination registry or daemon. Default to destination registry setting.
|
||||
|
||||
@@ -160,10 +179,30 @@ Bearer token for accessing the source registry.
|
||||
|
||||
Bearer token for accessing the destination registry.
|
||||
|
||||
**--dest-precompute-digests**
|
||||
|
||||
Precompute digests to ensure layers are not uploaded that already exist on the destination registry. Layers with initially unknown digests (ex. compressing "on the fly") will be temporarily streamed to disk.
|
||||
|
||||
**--retry-times**
|
||||
|
||||
The number of times to retry. Retry wait time will be exponentially increased based on the number of failed attempts.
|
||||
|
||||
**--src-username**
|
||||
|
||||
The username to access the source registry.
|
||||
|
||||
**--src-password**
|
||||
|
||||
The password to access the source registry.
|
||||
|
||||
**--dest-username**
|
||||
|
||||
The username to access the destination registry.
|
||||
|
||||
**--dest-password**
|
||||
|
||||
The password to access the destination registry.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
To just copy an image from one registry to another:
|
||||
|
||||
@@ -42,7 +42,7 @@ Use docker daemon host at _host_ (`docker-daemon:` transport only)
|
||||
|
||||
Print usage statement
|
||||
|
||||
**--no-creds** _bool-value_
|
||||
**--no-creds**
|
||||
|
||||
Access the registry anonymously.
|
||||
|
||||
@@ -64,6 +64,14 @@ Directory to use to share blobs across OCI repositories.
|
||||
|
||||
Require HTTPS and verify certificates when talking to the container registry or daemon. Default to registry.conf setting.
|
||||
|
||||
**--username**
|
||||
|
||||
The username to access the registry.
|
||||
|
||||
**--password**
|
||||
|
||||
The password to access the registry.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Mark image example/pause for deletion from the registry.example.com registry:
|
||||
|
||||
@@ -8,9 +8,12 @@ skopeo\-inspect - Return low-level information about _image-name_ in a registry.
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Return low-level information about _image-name_ in a registry
|
||||
Return low-level information about _image-name_ in a registry.
|
||||
See [skopeo(1)](skopeo.1.md) for the format of _image-name_.
|
||||
|
||||
_image-name_ name of image to retrieve information about
|
||||
The default output includes data from various sources: user input (**Name**), the remote repository, if any (**RepoTags**), the top-level manifest (**Digest**),
|
||||
and a per-architecture/OS image matching the current run-time environment (most other values).
|
||||
To see values for a different architecture/OS, use the **--override-os** / **--override-arch** options documented in [skopeo(1)](skopeo.1.md).
|
||||
|
||||
## OPTIONS
|
||||
|
||||
@@ -69,6 +72,18 @@ Directory to use to share blobs across OCI repositories.
|
||||
|
||||
Require HTTPS and verify certificates when talking to the container registry or daemon. Default to registry.conf setting.
|
||||
|
||||
**--username**
|
||||
|
||||
The username to access the registry.
|
||||
|
||||
**--password**
|
||||
|
||||
The password to access the registry.
|
||||
|
||||
**--no-tags**, **-n**
|
||||
|
||||
Do not list the available tags from the repository in the output. When `true`, the `RepoTags` array will be empty. Defaults to `false`, which includes all available tags.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
To review information for the image fedora from the docker.io registry:
|
||||
@@ -98,6 +113,42 @@ $ skopeo inspect docker://docker.io/fedora
|
||||
}
|
||||
```
|
||||
|
||||
To inspect python from the docker.io registry and not show the available tags:
|
||||
```sh
|
||||
$ skopeo inspect --no-tags docker://docker.io/library/python
|
||||
{
|
||||
"Name": "docker.io/library/python",
|
||||
"Digest": "sha256:5ca194a80ddff913ea49c8154f38da66a41d2b73028c5cf7e46bc3c1d6fda572",
|
||||
"RepoTags": [],
|
||||
"Created": "2021-10-05T23:40:54.936108045Z",
|
||||
"DockerVersion": "20.10.7",
|
||||
"Labels": null,
|
||||
"Architecture": "amd64",
|
||||
"Os": "linux",
|
||||
"Layers": [
|
||||
"sha256:df5590a8898bedd76f02205dc8caa5cc9863267dbcd8aac038bcd212688c1cc7",
|
||||
"sha256:705bb4cb554eb7751fd21a994f6f32aee582fbe5ea43037db6c43d321763992b",
|
||||
"sha256:519df5fceacdeaadeec563397b1d9f4d7c29c9f6eff879739cab6f0c144f49e1",
|
||||
"sha256:ccc287cbeddc96a0772397ca00ec85482a7b7f9a9fac643bfddd87b932f743db",
|
||||
"sha256:e3f8e6af58ed3a502f0c3c15dce636d9d362a742eb5b67770d0cfcb72f3a9884",
|
||||
"sha256:aebed27b2d86a5a3a2cbe186247911047a7e432b9d17daad8f226597c0ea4276",
|
||||
"sha256:54c32182bdcc3041bf64077428467109a70115888d03f7757dcf614ff6d95ebe",
|
||||
"sha256:cc8b7caedab13af07adf4836e13af2d4e9e54d794129b0fd4c83ece6b1112e86",
|
||||
"sha256:462c3718af1d5cdc050cfba102d06c26f78fe3b738ce2ca2eb248034b1738945"
|
||||
],
|
||||
"Env": [
|
||||
"PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"LANG=C.UTF-8",
|
||||
"GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D",
|
||||
"PYTHON_VERSION=3.10.0",
|
||||
"PYTHON_PIP_VERSION=21.2.4",
|
||||
"PYTHON_SETUPTOOLS_VERSION=57.5.0",
|
||||
"PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/d781367b97acf0ece7e9e304bf281e99b618bf10/public/get-pip.py",
|
||||
"PYTHON_GET_PIP_SHA256=01249aa3e58ffb3e1686b7141b4e9aac4d398ef4ac3012ed9dff8dd9f685ffe0"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
$ /bin/skopeo inspect --config docker://registry.fedoraproject.org/fedora --format "{{ .Architecture }}"
|
||||
amd64
|
||||
|
||||
@@ -27,7 +27,7 @@ Use certificates at _path_ (\*.crt, \*.cert, \*.key) to connect to the registry.
|
||||
|
||||
Print usage statement
|
||||
|
||||
**--no-creds** _bool-value_
|
||||
**--no-creds**
|
||||
|
||||
Access the registry anonymously.
|
||||
|
||||
@@ -43,6 +43,14 @@ The number of times to retry. Retry wait time will be exponentially increased ba
|
||||
|
||||
Require HTTPS and verify certificates when talking to the container registry or daemon. Default to registry.conf setting.
|
||||
|
||||
**--username**
|
||||
|
||||
The username to access the registry.
|
||||
|
||||
**--password**
|
||||
|
||||
The password to access 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.
|
||||
|
||||
@@ -25,6 +25,10 @@ Print usage statement
|
||||
|
||||
Write signature to _output file_.
|
||||
|
||||
**--passphrase-file**=_path_
|
||||
|
||||
The passphare to use when signing with the key ID from `--sign-by`. Only the first line will be read. A passphrase stored in a file is of questionable security if other users can read this file. Do not use this option if at all avoidable.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```sh
|
||||
|
||||
@@ -62,25 +62,29 @@ Print usage statement.
|
||||
|
||||
**--scoped** Prefix images with the source image path, so that multiple images with the same name can be stored at _destination_.
|
||||
|
||||
**--preserve-digests** Preserve the digests during copying. Fail if the digest cannot be preserved.
|
||||
|
||||
**--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.
|
||||
|
||||
**--sign-by**=_key-id_ Add a signature using that key ID for an image name corresponding to _destination-image_.
|
||||
|
||||
**--sign-passphrase-file**=_path_ The passphare to use when signing with the key ID from `--sign-by`. Only the first line will be read. A passphrase stored in a file is of questionable security if other users can read this file. Do not use this option if at all avoidable.
|
||||
|
||||
**--src-creds** _username[:password]_ for accessing the source registry.
|
||||
|
||||
**--dest-creds** _username[:password]_ for accessing the destination registry.
|
||||
|
||||
**--src-cert-dir** _path_ Use certificates (*.crt, *.cert, *.key) at _path_ to connect to the source registry or daemon.
|
||||
|
||||
**--src-no-creds** _bool-value_ Access the registry anonymously.
|
||||
**--src-no-creds** Access the registry anonymously.
|
||||
|
||||
**--src-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to a container source registry or daemon. Default to source registry entry in registry.conf setting.
|
||||
**--src-tls-verify**=_bool_ Require HTTPS and verify certificates when talking to a container source registry or daemon. Default to source registry entry in registry.conf setting.
|
||||
|
||||
**--dest-cert-dir** _path_ Use certificates (*.crt, *.cert, *.key) at _path_ to connect to the destination registry or daemon.
|
||||
|
||||
**--dest-no-creds** _bool-value_ Access the registry anonymously.
|
||||
**--dest-no-creds** Access the registry anonymously.
|
||||
|
||||
**--dest-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to a container destination registry or daemon. Default to destination registry entry in registry.conf setting.
|
||||
**--dest-tls-verify**=_bool_ Require HTTPS and verify certificates when talking to a container destination registry or daemon. Default to destination registry entry in registry.conf setting.
|
||||
|
||||
**--src-registry-token** _Bearer token_ for accessing the source registry.
|
||||
|
||||
@@ -91,6 +95,22 @@ Print usage statement.
|
||||
**--keep-going**
|
||||
If any errors occur during copying of images, those errors are logged and the process continues syncing rest of the images and finally fails at the end.
|
||||
|
||||
**--src-username**
|
||||
|
||||
The username to access the source registry.
|
||||
|
||||
**--src-password**
|
||||
|
||||
The password to access the source registry.
|
||||
|
||||
**--dest-username**
|
||||
|
||||
The username to access the destination registry.
|
||||
|
||||
**--dest-password**
|
||||
|
||||
The password to access the destination registry.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
### Synchronizing to a local directory
|
||||
|
||||
22
go.mod
22
go.mod
@@ -3,31 +3,23 @@ module github.com/containers/skopeo
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/bits-and-blooms/bitset v1.2.1 // indirect
|
||||
github.com/containerd/containerd v1.5.7 // indirect
|
||||
github.com/containers/common v0.46.0
|
||||
github.com/containers/image/v5 v5.16.1
|
||||
github.com/containers/common v0.47.4
|
||||
github.com/containers/image/v5 v5.19.1
|
||||
github.com/containers/ocicrypt v1.1.2
|
||||
github.com/containers/storage v1.37.0
|
||||
github.com/docker/docker v20.10.9+incompatible
|
||||
github.com/containers/storage v1.38.2
|
||||
github.com/docker/docker v20.10.12+incompatible
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.0.2-0.20210819154149-5ad6f50d6283
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202193544-a5463b7f9c84
|
||||
github.com/opencontainers/image-tools v1.0.0-rc3
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.11.1 // indirect
|
||||
github.com/russross/blackfriday v2.0.0+incompatible // indirect
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/spf13/cobra v1.3.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
|
||||
golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b // indirect
|
||||
golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211005153810-c76a74d43a8e // indirect
|
||||
google.golang.org/grpc v1.41.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
56
install.md
56
install.md
@@ -1,4 +1,4 @@
|
||||
# Installing from packages
|
||||
# Installing Skopeo
|
||||
|
||||
## Distribution Packages
|
||||
`skopeo` may already be packaged in your distribution.
|
||||
@@ -91,6 +91,20 @@ request](https://github.com/containers/skopeo/issues/715) and contributions are
|
||||
always welcome.
|
||||
|
||||
|
||||
## Container Images
|
||||
|
||||
Skopeo container images are available at `quay.io/skopeo/stable:latest`.
|
||||
For example,
|
||||
|
||||
```bash
|
||||
podman run docker://quay.io/skopeo/stable:latest copy --help
|
||||
```
|
||||
|
||||
[Read more](./contrib/skopeoimage/README.md).
|
||||
|
||||
|
||||
## Building from Source
|
||||
|
||||
Otherwise, read on for building and installing it from source:
|
||||
|
||||
To build the `skopeo` binary you need at least Go 1.12.
|
||||
@@ -98,8 +112,6 @@ 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
|
||||
@@ -191,3 +203,41 @@ Finally, after the binary and documentation is built:
|
||||
```bash
|
||||
sudo make install
|
||||
```
|
||||
|
||||
### Building a static binary
|
||||
|
||||
There have been efforts in the past to produce and maintain static builds, but the maintainers prefer to run Skopeo using distro packages or within containers. This is because static builds of Skopeo tend to be unreliable and functionally restricted. Specifically:
|
||||
- Some features of Skopeo depend on non-Go libraries like `libgpgme` and `libdevmapper`.
|
||||
- Generating static Go binaries uses native Go libraries, which don't support e.g. `.local` or LDAP-based name resolution.
|
||||
|
||||
That being said, if you would like to build Skopeo statically, you might be able to do it by combining all the following steps.
|
||||
- Export environment variable `CGO_ENABLED=0` (disabling CGO causes Go to prefer native libraries when possible, instead of dynamically linking against system libraries).
|
||||
- Set the `BUILDTAGS=containers_image_openpgp` Make variable (this remove the dependency on `libgpgme` and its companion libraries).
|
||||
- Clear the `GO_DYN_FLAGS` Make variable (which otherwise seems to force the creation of a dynamic executable).
|
||||
|
||||
The following command implements these steps to produce a static binary in the `bin` subdirectory of the repository:
|
||||
|
||||
```bash
|
||||
docker run -v $PWD:/src -w /src -e CGO_ENABLED=0 golang \
|
||||
make BUILDTAGS=containers_image_openpgp GO_DYN_FLAGS=
|
||||
```
|
||||
|
||||
Keep in mind that the resulting binary is unsupported and might crash randomly. Only use if you know what you're doing!
|
||||
|
||||
For more information, history, and context about static builds, check the following issues:
|
||||
|
||||
- [#391] - Consider distributing statically built binaries as part of release
|
||||
- [#669] - Static build fails with segmentation violation
|
||||
- [#670] - Fixing static binary build using container
|
||||
- [#755] - Remove static and in-container targets from Makefile
|
||||
- [#932] - Add nix derivation for static builds
|
||||
- [#1336] - Unable to run skopeo on Fedora 30 (due to dyn lib dependency)
|
||||
- [#1478] - Publish binary releases to GitHub (request+discussion)
|
||||
|
||||
[#391]: https://github.com/containers/skopeo/issues/391
|
||||
[#669]: https://github.com/containers/skopeo/issues/669
|
||||
[#670]: https://github.com/containers/skopeo/issues/670
|
||||
[#755]: https://github.com/containers/skopeo/issues/755
|
||||
[#932]: https://github.com/containers/skopeo/issues/932
|
||||
[#1336]: https://github.com/containers/skopeo/issues/1336
|
||||
[#1478]: https://github.com/containers/skopeo/issues/1478
|
||||
|
||||
@@ -123,10 +123,10 @@ 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", 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)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", knownListImage, "oci:"+oci1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "oci:"+oci1, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "dir:"+dir1, "oci:"+oci2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "oci:"+oci2, "dir:"+dir2)
|
||||
assertDirImagesAreEqual(c, dir1, dir2)
|
||||
out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2)
|
||||
c.Assert(out, check.Equals, "")
|
||||
@@ -145,15 +145,30 @@ 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", knownListImage, "oci:"+oci1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci1, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "--format", "oci", knownListImage, "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "dir:"+dir2, "oci:"+oci2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", knownListImage, "oci:"+oci1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "oci:"+oci1, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "--format", "oci", knownListImage, "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "dir:"+dir2, "oci:"+oci2)
|
||||
assertDirImagesAreEqual(c, dir1, dir2)
|
||||
out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2)
|
||||
c.Assert(out, check.Equals, "")
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopyNoneWithManifestList(c *check.C) {
|
||||
dir1, err := ioutil.TempDir("", "copy-all-manifest-list-dir")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=index-only", knownListImage, "dir:"+dir1)
|
||||
|
||||
manifestPath := filepath.Join(dir1, "manifest.json")
|
||||
readManifest, err := ioutil.ReadFile(manifestPath)
|
||||
c.Assert(err, check.IsNil)
|
||||
mimeType := manifest.GuessMIMEType(readManifest)
|
||||
c.Assert(mimeType, check.Equals, "application/vnd.docker.distribution.manifest.list.v2+json")
|
||||
out := combinedOutputOfCommand(c, "ls", "-1", dir1)
|
||||
c.Assert(out, check.Equals, "manifest.json\nversion\n")
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopyWithManifestListConverge(c *check.C) {
|
||||
oci1, err := ioutil.TempDir("", "copy-all-manifest-list-oci")
|
||||
c.Assert(err, check.IsNil)
|
||||
@@ -168,9 +183,9 @@ func (s *CopySuite) TestCopyWithManifestListConverge(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", knownListImage, "oci:"+oci1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci1, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "oci:"+oci1, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--format", "oci", knownListImage, "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "dir:"+dir2, "oci:"+oci2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "dir:"+dir2, "oci:"+oci2)
|
||||
assertDirImagesAreEqual(c, dir1, dir2)
|
||||
out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2)
|
||||
c.Assert(out, check.Equals, "")
|
||||
@@ -181,7 +196,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", knownListImage, "containers-storage:"+storage+"test")
|
||||
assertSkopeoFails(c, `.*destination transport .* does not support copying multiple images as a group.*`, "copy", "--multi-arch=all", knownListImage, "containers-storage:"+storage+"test")
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopyWithManifestListStorage(c *check.C) {
|
||||
@@ -239,7 +254,7 @@ func (s *CopySuite) TestCopyWithManifestListDigest(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
digest := manifestDigest.String()
|
||||
assertSkopeoSucceeds(c, "", "copy", knownListImage+"@"+digest, "dir:"+dir1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", knownListImage+"@"+digest, "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", knownListImage+"@"+digest, "dir:"+dir2)
|
||||
assertSkopeoSucceeds(c, "", "copy", "dir:"+dir1, "oci:"+oci1)
|
||||
assertSkopeoSucceeds(c, "", "copy", "dir:"+dir2, "oci:"+oci2)
|
||||
out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2)
|
||||
@@ -1190,12 +1205,14 @@ func (s *SkopeoSuite) TestCopySrcAndDestWithAuth(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopyNoPanicOnHTTPResponseWithoutTLSVerifyFalse(c *check.C) {
|
||||
topDir, err := ioutil.TempDir("", "no-panic-on-https-response-without-tls-verify-false")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(topDir)
|
||||
|
||||
const ourRegistry = "docker://" + v2DockerRegistryURL + "/"
|
||||
|
||||
// dir:test isn't created beforehand just because we already know this could
|
||||
// just fail when evaluating the src
|
||||
assertSkopeoFails(c, ".*server gave HTTP response to HTTPS client.*",
|
||||
"copy", ourRegistry+"foobar", "dir:test")
|
||||
"copy", ourRegistry+"foobar", "dir:"+topDir)
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopySchemaConversion(c *check.C) {
|
||||
@@ -1231,6 +1248,15 @@ func (s *CopySuite) TestCopyManifestConversion(c *check.C) {
|
||||
verifyManifestMIMEType(c, destDir2, manifest.DockerV2Schema2MediaType)
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopyPreserveDigests(c *check.C) {
|
||||
topDir, err := ioutil.TempDir("", "preserve-digests")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(topDir)
|
||||
|
||||
assertSkopeoSucceeds(c, "", "copy", knownListImage, "--multi-arch=all", "--preserve-digests", "dir:"+topDir)
|
||||
assertSkopeoFails(c, ".*Instructed to preserve digests.*", "copy", knownListImage, "--multi-arch=all", "--preserve-digests", "--format=oci", "dir:"+topDir)
|
||||
}
|
||||
|
||||
func (s *CopySuite) testCopySchemaConversionRegistries(c *check.C, schema1Registry, schema2Registry string) {
|
||||
topDir, err := ioutil.TempDir("", "schema-conversion")
|
||||
c.Assert(err, check.IsNil)
|
||||
@@ -1279,8 +1305,10 @@ func (s *SkopeoSuite) TestFailureCopySrcWithMirrorsUnavailable(c *check.C) {
|
||||
dir, err := ioutil.TempDir("", "copy-mirror")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
assertSkopeoFails(c, ".*no such host.*", "--registries-conf="+regConfFixture, "copy",
|
||||
"docker://invalid.invalid/busybox", "dir:"+dir)
|
||||
// .invalid domains are, per RFC 6761, supposed to result in NXDOMAIN.
|
||||
// With systemd-resolved (used only via NSS?), we instead seem to get “Temporary failure in name resolution”
|
||||
assertSkopeoFails(c, ".*(no such host|Temporary failure in name resolution).*",
|
||||
"--registries-conf="+regConfFixture, "copy", "docker://invalid.invalid/busybox", "dir:"+dir)
|
||||
}
|
||||
|
||||
func (s *SkopeoSuite) TestSuccessCopySrcWithMirrorAndPrefix(c *check.C) {
|
||||
@@ -1295,8 +1323,10 @@ func (s *SkopeoSuite) TestFailureCopySrcWithMirrorAndPrefixUnavailable(c *check.
|
||||
dir, err := ioutil.TempDir("", "copy-mirror")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
assertSkopeoFails(c, ".*no such host.*", "--registries-conf="+regConfFixture, "copy",
|
||||
"docker://gcr.invalid/wrong/prefix/busybox", "dir:"+dir)
|
||||
// .invalid domains are, per RFC 6761, supposed to result in NXDOMAIN.
|
||||
// With systemd-resolved (used only via NSS?), we instead seem to get “Temporary failure in name resolution”
|
||||
assertSkopeoFails(c, ".*(no such host|Temporary failure in name resolution).*",
|
||||
"--registries-conf="+regConfFixture, "copy", "docker://gcr.invalid/wrong/prefix/busybox", "dir:"+dir)
|
||||
}
|
||||
|
||||
func (s *CopySuite) TestCopyFailsWhenReferenceIsInvalid(c *check.C) {
|
||||
|
||||
12
integration/procutils.go
Normal file
12
integration/procutils.go
Normal file
@@ -0,0 +1,12 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// cmdLifecycleToParentIfPossible tries to exit if the parent process exits (only works on Linux)
|
||||
func cmdLifecycleToParentIfPossible(c *exec.Cmd) {
|
||||
}
|
||||
14
integration/procutils_linux.go
Normal file
14
integration/procutils_linux.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// cmdLifecyleToParentIfPossible is a thin wrapper around prctl(PR_SET_PDEATHSIG)
|
||||
// on Linux.
|
||||
func cmdLifecycleToParentIfPossible(c *exec.Cmd) {
|
||||
c.SysProcAttr = &syscall.SysProcAttr{
|
||||
Pdeathsig: syscall.SIGTERM,
|
||||
}
|
||||
}
|
||||
307
integration/proxy_test.go
Normal file
307
integration/proxy_test.go
Normal file
@@ -0,0 +1,307 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
|
||||
"github.com/containers/image/v5/manifest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// This image is known to be x86_64 only right now
|
||||
const knownNotManifestListedImage_x8664 = "docker://quay.io/coreos/11bot"
|
||||
|
||||
const expectedProxySemverMajor = "0.2"
|
||||
|
||||
// request is copied from proxy.go
|
||||
// We intentionally copy to ensure that we catch any unexpected "API" changes
|
||||
// in the JSON.
|
||||
type request struct {
|
||||
// Method is the name of the function
|
||||
Method string `json:"method"`
|
||||
// Args is the arguments (parsed inside the function)
|
||||
Args []interface{} `json:"args"`
|
||||
}
|
||||
|
||||
// reply is copied from proxy.go
|
||||
type reply struct {
|
||||
// Success is true if and only if the call succeeded.
|
||||
Success bool `json:"success"`
|
||||
// Value is an arbitrary value (or values, as array/map) returned from the call.
|
||||
Value interface{} `json:"value"`
|
||||
// PipeID is an index into open pipes, and should be passed to FinishPipe
|
||||
PipeID uint32 `json:"pipeid"`
|
||||
// Error should be non-empty if Success == false
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// maxMsgSize is also copied from proxy.go
|
||||
const maxMsgSize = 32 * 1024
|
||||
|
||||
type proxy struct {
|
||||
c *net.UnixConn
|
||||
}
|
||||
|
||||
type pipefd struct {
|
||||
// id is the remote identifier "pipeid"
|
||||
id uint
|
||||
fd *os.File
|
||||
}
|
||||
|
||||
func (self *proxy) call(method string, args []interface{}) (rval interface{}, fd *pipefd, err error) {
|
||||
req := request{
|
||||
Method: method,
|
||||
Args: args,
|
||||
}
|
||||
reqbuf, err := json.Marshal(&req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n, err := self.c.Write(reqbuf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != len(reqbuf) {
|
||||
err = fmt.Errorf("short write during call of %d bytes", n)
|
||||
return
|
||||
}
|
||||
oob := make([]byte, syscall.CmsgSpace(1))
|
||||
replybuf := make([]byte, maxMsgSize)
|
||||
n, oobn, _, _, err := self.c.ReadMsgUnix(replybuf, oob)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("reading reply: %v", err)
|
||||
return
|
||||
}
|
||||
var reply reply
|
||||
err = json.Unmarshal(replybuf[0:n], &reply)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to parse reply: %w", err)
|
||||
return
|
||||
}
|
||||
if !reply.Success {
|
||||
err = fmt.Errorf("remote error: %s", reply.Error)
|
||||
return
|
||||
}
|
||||
|
||||
if reply.PipeID > 0 {
|
||||
var scms []syscall.SocketControlMessage
|
||||
scms, err = syscall.ParseSocketControlMessage(oob[:oobn])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to parse control message: %v", err)
|
||||
return
|
||||
}
|
||||
if len(scms) != 1 {
|
||||
err = fmt.Errorf("Expected 1 received fd, found %d", len(scms))
|
||||
return
|
||||
}
|
||||
var fds []int
|
||||
fds, err = syscall.ParseUnixRights(&scms[0])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to parse unix rights: %v", err)
|
||||
return
|
||||
}
|
||||
fd = &pipefd{
|
||||
fd: os.NewFile(uintptr(fds[0]), "replyfd"),
|
||||
id: uint(reply.PipeID),
|
||||
}
|
||||
}
|
||||
|
||||
rval = reply.Value
|
||||
return
|
||||
}
|
||||
|
||||
func (self *proxy) callNoFd(method string, args []interface{}) (rval interface{}, err error) {
|
||||
var fd *pipefd
|
||||
rval, fd, err = self.call(method, args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if fd != nil {
|
||||
err = fmt.Errorf("Unexpected fd from method %s", method)
|
||||
return
|
||||
}
|
||||
return rval, nil
|
||||
}
|
||||
|
||||
func (self *proxy) callReadAllBytes(method string, args []interface{}) (rval interface{}, buf []byte, err error) {
|
||||
var fd *pipefd
|
||||
rval, fd, err = self.call(method, args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if fd == nil {
|
||||
err = fmt.Errorf("Expected fd from method %s", method)
|
||||
return
|
||||
}
|
||||
fetchchan := make(chan byteFetch)
|
||||
go func() {
|
||||
manifestBytes, err := ioutil.ReadAll(fd.fd)
|
||||
fetchchan <- byteFetch{
|
||||
content: manifestBytes,
|
||||
err: err,
|
||||
}
|
||||
}()
|
||||
_, err = self.callNoFd("FinishPipe", []interface{}{fd.id})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case fetchRes := <-fetchchan:
|
||||
err = fetchRes.err
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf = fetchRes.content
|
||||
case <-time.After(5 * time.Minute):
|
||||
err = fmt.Errorf("timed out during proxy fetch")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func newProxy() (*proxy, error) {
|
||||
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_SEQPACKET, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
myfd := os.NewFile(uintptr(fds[0]), "myfd")
|
||||
defer myfd.Close()
|
||||
theirfd := os.NewFile(uintptr(fds[1]), "theirfd")
|
||||
defer theirfd.Close()
|
||||
|
||||
mysock, err := net.FileConn(myfd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Note ExtraFiles starts at 3
|
||||
proc := exec.Command("skopeo", "experimental-image-proxy", "--sockfd", "3")
|
||||
proc.Stderr = os.Stderr
|
||||
cmdLifecycleToParentIfPossible(proc)
|
||||
proc.ExtraFiles = append(proc.ExtraFiles, theirfd)
|
||||
|
||||
if err = proc.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &proxy{
|
||||
c: mysock.(*net.UnixConn),
|
||||
}
|
||||
|
||||
v, err := p.callNoFd("Initialize", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
semver, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("proxy Initialize: Unexpected value %T", v)
|
||||
}
|
||||
if !strings.HasPrefix(semver, expectedProxySemverMajor) {
|
||||
return nil, fmt.Errorf("Unexpected semver %s", semver)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
check.Suite(&ProxySuite{})
|
||||
}
|
||||
|
||||
type ProxySuite struct {
|
||||
}
|
||||
|
||||
func (s *ProxySuite) SetUpSuite(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *ProxySuite) TearDownSuite(c *check.C) {
|
||||
}
|
||||
|
||||
type byteFetch struct {
|
||||
content []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func runTestGetManifestAndConfig(p *proxy, img string) error {
|
||||
v, err := p.callNoFd("OpenImage", []interface{}{knownNotManifestListedImage_x8664})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imgidv, ok := v.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("OpenImage return value is %T", v)
|
||||
}
|
||||
imgid := uint32(imgidv)
|
||||
|
||||
v, manifestBytes, err := p.callReadAllBytes("GetManifest", []interface{}{imgid})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = manifest.OCI1FromManifest(manifestBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, configBytes, err := p.callReadAllBytes("GetFullConfig", []interface{}{imgid})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var config imgspecv1.Image
|
||||
err = json.Unmarshal(configBytes, &config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate that the image config seems sane
|
||||
if config.Architecture == "" {
|
||||
return fmt.Errorf("No architecture found")
|
||||
}
|
||||
if len(config.Config.Cmd) == 0 && len(config.Config.Entrypoint) == 0 {
|
||||
return fmt.Errorf("No CMD or ENTRYPOINT set")
|
||||
}
|
||||
|
||||
// Also test this legacy interface
|
||||
v, ctrconfigBytes, err := p.callReadAllBytes("GetConfig", []interface{}{imgid})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ctrconfig imgspecv1.ImageConfig
|
||||
err = json.Unmarshal(ctrconfigBytes, &ctrconfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate that the config seems sane
|
||||
if len(ctrconfig.Cmd) == 0 && len(ctrconfig.Entrypoint) == 0 {
|
||||
return fmt.Errorf("No CMD or ENTRYPOINT set")
|
||||
}
|
||||
|
||||
_, err = p.callNoFd("CloseImage", []interface{}{imgid})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ProxySuite) TestProxy(c *check.C) {
|
||||
p, err := newProxy()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = runTestGetManifestAndConfig(p, knownNotManifestListedImage_x8664)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Testing image %s: %v", knownNotManifestListedImage_x8664, err)
|
||||
}
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = runTestGetManifestAndConfig(p, knownListImage)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Testing image %s: %v", knownListImage, err)
|
||||
}
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
@@ -163,6 +163,22 @@ func (s *SyncSuite) TestDocker2DirTaggedAll(c *check.C) {
|
||||
c.Assert(out, check.Equals, "")
|
||||
}
|
||||
|
||||
func (s *SyncSuite) TestPreserveDigests(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
|
||||
|
||||
// copy docker => dir
|
||||
assertSkopeoSucceeds(c, "", "copy", "--all", "--preserve-digests", "docker://"+image, "dir:"+tmpDir)
|
||||
_, err = os.Stat(path.Join(tmpDir, "manifest.json"))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
assertSkopeoFails(c, ".*Instructed to preserve digests.*", "copy", "--all", "--preserve-digests", "--format=oci", "docker://"+image, "dir:"+tmpDir)
|
||||
}
|
||||
|
||||
func (s *SyncSuite) TestScoped(c *check.C) {
|
||||
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
|
||||
image := pullableTaggedImage
|
||||
|
||||
@@ -27,11 +27,19 @@ 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'.
|
||||
# As of Skopeo 1.6, (skopeo inspect)'s output 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.
|
||||
@@ -108,4 +116,15 @@ END_EXPECT
|
||||
"os - variant - architecture of $img"
|
||||
}
|
||||
|
||||
@test "inspect: don't list tags" {
|
||||
remote_image=docker://quay.io/fedora/fedora
|
||||
# use --no-tags to not list any tags
|
||||
run_skopeo inspect --no-tags $remote_image
|
||||
inspect_output=$output
|
||||
# extract the content of "RepoTags" property from the JSON output
|
||||
repo_tags=$(jq '.RepoTags[]' <<<"$inspect_output")
|
||||
# verify that the RepoTags was empty
|
||||
expect_output --from="$repo_tags" "" "inspect --no-tags was expected to return empty RepoTags[]"
|
||||
}
|
||||
|
||||
# vim: filetype=sh
|
||||
|
||||
@@ -125,6 +125,10 @@ function setup() {
|
||||
run podman --root $TESTDIR/podmanroot images
|
||||
expect_output --substring "mine"
|
||||
|
||||
# rootless cleanup needs to be done with unshare due to subuids
|
||||
if [[ "$(id -u)" != "0" ]]; then
|
||||
run podman unshare rm -rf $TESTDIR/podmanroot
|
||||
fi
|
||||
}
|
||||
|
||||
# shared blob directory
|
||||
@@ -144,6 +148,16 @@ function setup() {
|
||||
diff -urN $shareddir $dir2/blobs
|
||||
}
|
||||
|
||||
@test "copy: sif image" {
|
||||
type -path fakeroot || skip "'fakeroot' tool not available"
|
||||
|
||||
local localimg=dir:$TESTDIR/dir
|
||||
|
||||
run_skopeo copy sif:${TEST_SOURCE_DIR}/testdata/busybox_latest.sif $localimg
|
||||
run_skopeo inspect $localimg --format "{{.Architecture}}"
|
||||
expect_output "amd64"
|
||||
}
|
||||
|
||||
teardown() {
|
||||
podman rm -f reg
|
||||
|
||||
|
||||
@@ -12,6 +12,13 @@ function setup() {
|
||||
export GNUPGHOME=$TESTDIR/skopeo-gpg
|
||||
mkdir --mode=0700 $GNUPGHOME
|
||||
|
||||
PASSPHRASE_FILE=$TESTDIR/passphrase-file
|
||||
passphrase=$(random_string 20)
|
||||
echo $passphrase > $PASSPHRASE_FILE
|
||||
|
||||
PASSPHRASE_FILE_WRONG=$TESTDIR/passphrase-file-wrong
|
||||
echo $(random_string 10) > $PASSPHRASE_FILE_WRONG
|
||||
|
||||
# gpg on f30 needs this, otherwise:
|
||||
# gpg: agent_genkey failed: Inappropriate ioctl for device
|
||||
# ...but gpg on f29 (and, probably, Ubuntu) doesn't grok this
|
||||
@@ -21,7 +28,7 @@ function setup() {
|
||||
fi
|
||||
|
||||
for k in alice bob;do
|
||||
gpg --batch $GPGOPTS --gen-key --passphrase '' <<END_GPG
|
||||
gpg --batch $GPGOPTS --gen-key --passphrase $passphrase <<END_GPG
|
||||
Key-Type: RSA
|
||||
Name-Real: Test key - $k
|
||||
Name-email: $k@test.redhat.com
|
||||
@@ -81,8 +88,18 @@ END_POLICY_JSON
|
||||
start_registry reg
|
||||
}
|
||||
|
||||
function kill_gpg_agent {
|
||||
# Kill the running gpg-agent to drop unlocked keys. This allows for testing
|
||||
# handling of invalid passphrases.
|
||||
run gpgconf --kill gpg-agent
|
||||
if [ "$status" -ne 0 ]; then
|
||||
die "could not restart gpg-agent: $output"
|
||||
fi
|
||||
}
|
||||
|
||||
@test "signing" {
|
||||
run_skopeo '?' standalone-sign /dev/null busybox alice@test.redhat.com -o /dev/null
|
||||
kill_gpg_agent
|
||||
run_skopeo '?' standalone-sign /dev/null busybox alice@test.redhat.com -o /dev/null --passphrase-file $PASSPHRASE_FILE
|
||||
if [[ "$output" =~ 'signing is not supported' ]]; then
|
||||
skip "skopeo built without support for creating signatures"
|
||||
return 1
|
||||
@@ -100,7 +117,8 @@ END_POLICY_JSON
|
||||
while read path sig comments; do
|
||||
local sign_opt=
|
||||
if [[ $sig != '-' ]]; then
|
||||
sign_opt="--sign-by=${sig}@test.redhat.com"
|
||||
kill_gpg_agent
|
||||
sign_opt=" --sign-passphrase-file=$PASSPHRASE_FILE --sign-by=${sig}@test.redhat.com"
|
||||
fi
|
||||
run_skopeo --registries.d $REGISTRIES_D \
|
||||
copy --dest-tls-verify=false \
|
||||
@@ -144,7 +162,8 @@ END_TESTS
|
||||
}
|
||||
|
||||
@test "signing: remove signature" {
|
||||
run_skopeo '?' standalone-sign /dev/null busybox alice@test.redhat.com -o /dev/null
|
||||
kill_gpg_agent
|
||||
run_skopeo '?' standalone-sign /dev/null busybox alice@test.redhat.com -o /dev/null --passphrase-file $PASSPHRASE_FILE
|
||||
if [[ "$output" =~ 'signing is not supported' ]]; then
|
||||
skip "skopeo built without support for creating signatures"
|
||||
return 1
|
||||
@@ -157,11 +176,24 @@ END_TESTS
|
||||
run_skopeo copy docker://quay.io/libpod/busybox:latest \
|
||||
dir:$TESTDIR/busybox
|
||||
# Push a signed image
|
||||
kill_gpg_agent
|
||||
run_skopeo --registries.d $REGISTRIES_D \
|
||||
copy --dest-tls-verify=false \
|
||||
--sign-by=alice@test.redhat.com \
|
||||
--sign-passphrase-file $PASSPHRASE_FILE \
|
||||
dir:$TESTDIR/busybox \
|
||||
docker://localhost:5000/myns/alice:signed
|
||||
|
||||
# Wrong passphrase file
|
||||
kill_gpg_agent
|
||||
run_skopeo 1 --registries.d $REGISTRIES_D \
|
||||
copy --dest-tls-verify=false \
|
||||
--sign-by=alice@test.redhat.com \
|
||||
--sign-passphrase-file $PASSPHRASE_FILE_WRONG \
|
||||
dir:$TESTDIR/busybox \
|
||||
docker://localhost:5000/myns/alice:signed
|
||||
expect_output --substring "Bad passphrase"
|
||||
|
||||
# Fetch the image with signature
|
||||
run_skopeo --registries.d $REGISTRIES_D \
|
||||
--policy $POLICY_JSON \
|
||||
@@ -180,7 +212,8 @@ END_TESTS
|
||||
}
|
||||
|
||||
@test "signing: standalone" {
|
||||
run_skopeo '?' standalone-sign /dev/null busybox alice@test.redhat.com -o /dev/null
|
||||
kill_gpg_agent
|
||||
run_skopeo '?' standalone-sign /dev/null busybox alice@test.redhat.com -o /dev/null --passphrase-file $PASSPHRASE_FILE
|
||||
if [[ "$output" =~ 'signing is not supported' ]]; then
|
||||
skip "skopeo built without support for creating signatures"
|
||||
return 1
|
||||
@@ -196,7 +229,9 @@ END_TESTS
|
||||
docker://localhost:5000/busybox:latest \
|
||||
dir:$TESTDIR/busybox
|
||||
# Standalone sign
|
||||
kill_gpg_agent
|
||||
run_skopeo standalone-sign -o $TESTDIR/busybox.signature \
|
||||
--passphrase-file $PASSPHRASE_FILE \
|
||||
$TESTDIR/busybox/manifest.json \
|
||||
localhost:5000/busybox:latest \
|
||||
alice@test.redhat.com
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
SKOPEO_BINARY=${SKOPEO_BINARY:-$(dirname ${BASH_SOURCE})/../skopeo}
|
||||
# Directory containing system test sources
|
||||
TEST_SOURCE_DIR=${TEST_SOURCE_DIR:-$(dirname ${BASH_SOURCE})}
|
||||
|
||||
# Skopeo executable
|
||||
SKOPEO_BINARY=${SKOPEO_BINARY:-${TEST_SOURCE_DIR}/../bin/skopeo}
|
||||
|
||||
# Default timeout for a skopeo command.
|
||||
SKOPEO_TIMEOUT=${SKOPEO_TIMEOUT:-300}
|
||||
@@ -356,7 +360,7 @@ start_registry() {
|
||||
return
|
||||
fi
|
||||
|
||||
timeout=$(expr $timeout - 1)
|
||||
timeout=$(( timeout - 1 ))
|
||||
sleep 1
|
||||
done
|
||||
log_and_run $PODMAN logs $name
|
||||
|
||||
BIN
systemtest/testdata/busybox_latest.sif
vendored
Executable file
BIN
systemtest/testdata/busybox_latest.sif
vendored
Executable file
Binary file not shown.
41
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
41
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
@@ -1,10 +1,6 @@
|
||||
## TOML parser and encoder for Go with reflection
|
||||
|
||||
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
|
||||
reflection interface similar to Go's standard library `json` and `xml`
|
||||
packages. This package also supports the `encoding.TextUnmarshaler` and
|
||||
`encoding.TextMarshaler` interfaces so that you can define custom data
|
||||
representations. (There is an example of this below.)
|
||||
packages.
|
||||
|
||||
Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).
|
||||
|
||||
@@ -16,26 +12,25 @@ v0.4.0`).
|
||||
|
||||
This library requires Go 1.13 or newer; install it with:
|
||||
|
||||
$ go get github.com/BurntSushi/toml
|
||||
% go get github.com/BurntSushi/toml@latest
|
||||
|
||||
It also comes with a TOML validator CLI tool:
|
||||
|
||||
$ go get github.com/BurntSushi/toml/cmd/tomlv
|
||||
$ tomlv some-toml-file.toml
|
||||
% go install github.com/BurntSushi/toml/cmd/tomlv@latest
|
||||
% tomlv some-toml-file.toml
|
||||
|
||||
### Testing
|
||||
This package passes all tests in [toml-test] for both the decoder and the
|
||||
encoder.
|
||||
|
||||
This package passes all tests in
|
||||
[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder
|
||||
and the encoder.
|
||||
[toml-test]: https://github.com/BurntSushi/toml-test
|
||||
|
||||
### Examples
|
||||
This package works similar to how the Go standard library handles XML and JSON.
|
||||
Namely, data is loaded into Go values via reflection.
|
||||
|
||||
This package works similarly to how the Go standard library handles XML and
|
||||
JSON. Namely, data is loaded into Go values via reflection.
|
||||
|
||||
For the simplest example, consider some TOML file as just a list of keys
|
||||
and values:
|
||||
For the simplest example, consider some TOML file as just a list of keys and
|
||||
values:
|
||||
|
||||
```toml
|
||||
Age = 25
|
||||
@@ -61,9 +56,8 @@ And then decoded with:
|
||||
|
||||
```go
|
||||
var conf Config
|
||||
if _, err := toml.Decode(tomlData, &conf); err != nil {
|
||||
// handle error
|
||||
}
|
||||
err := toml.Decode(tomlData, &conf)
|
||||
// handle error
|
||||
```
|
||||
|
||||
You can also use struct tags if your struct field name doesn't map to a TOML
|
||||
@@ -75,15 +69,14 @@ some_key_NAME = "wat"
|
||||
|
||||
```go
|
||||
type TOML struct {
|
||||
ObscureKey string `toml:"some_key_NAME"`
|
||||
ObscureKey string `toml:"some_key_NAME"`
|
||||
}
|
||||
```
|
||||
|
||||
Beware that like other most other decoders **only exported fields** are
|
||||
considered when encoding and decoding; private fields are silently ignored.
|
||||
|
||||
### Using the `encoding.TextUnmarshaler` interface
|
||||
|
||||
### Using the `Marshaler` and `encoding.TextUnmarshaler` interfaces
|
||||
Here's an example that automatically parses duration strings into
|
||||
`time.Duration` values:
|
||||
|
||||
@@ -136,7 +129,6 @@ To target TOML specifically you can implement `UnmarshalTOML` TOML interface in
|
||||
a similar way.
|
||||
|
||||
### More complex usage
|
||||
|
||||
Here's an example of how to load the example from the official spec page:
|
||||
|
||||
```toml
|
||||
@@ -216,5 +208,4 @@ type clients struct {
|
||||
Note that a case insensitive match will be tried if an exact match can't be
|
||||
found.
|
||||
|
||||
A working example of the above can be found in `_examples/example.{go,toml}`.
|
||||
|
||||
A working example of the above can be found in `_example/example.{go,toml}`.
|
||||
|
||||
133
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
133
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
@@ -9,7 +9,6 @@ import (
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Unmarshaler is the interface implemented by objects that can unmarshal a
|
||||
@@ -40,6 +39,13 @@ type Primitive struct {
|
||||
context Key
|
||||
}
|
||||
|
||||
// The significand precision for float32 and float64 is 24 and 53 bits; this is
|
||||
// the range a natural number can be stored in a float without loss of data.
|
||||
const (
|
||||
maxSafeFloat32Int = 16777215 // 2^24-1
|
||||
maxSafeFloat64Int = 9007199254740991 // 2^53-1
|
||||
)
|
||||
|
||||
// PrimitiveDecode is just like the other `Decode*` functions, except it
|
||||
// decodes a TOML value that has already been parsed. Valid primitive values
|
||||
// can *only* be obtained from values filled by the decoder functions,
|
||||
@@ -100,18 +106,38 @@ func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{r: r}
|
||||
}
|
||||
|
||||
var (
|
||||
unmarshalToml = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
unmarshalText = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
// Decode TOML data in to the pointer `v`.
|
||||
func (dec *Decoder) Decode(v interface{}) (MetaData, error) {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
|
||||
s := "%q"
|
||||
if reflect.TypeOf(v) == nil {
|
||||
s = "%v"
|
||||
}
|
||||
|
||||
return MetaData{}, e("cannot decode to non-pointer "+s, reflect.TypeOf(v))
|
||||
}
|
||||
if rv.IsNil() {
|
||||
return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
|
||||
return MetaData{}, e("cannot decode to nil value of %q", reflect.TypeOf(v))
|
||||
}
|
||||
|
||||
// TODO: have parser should read from io.Reader? Or at the very least, make
|
||||
// it read from []byte rather than string
|
||||
// Check if this is a supported type: struct, map, interface{}, or something
|
||||
// that implements UnmarshalTOML or UnmarshalText.
|
||||
rv = indirect(rv)
|
||||
rt := rv.Type()
|
||||
if rv.Kind() != reflect.Struct && rv.Kind() != reflect.Map &&
|
||||
!(rv.Kind() == reflect.Interface && rv.NumMethod() == 0) &&
|
||||
!rt.Implements(unmarshalToml) && !rt.Implements(unmarshalText) {
|
||||
return MetaData{}, e("cannot decode to type %s", rt)
|
||||
}
|
||||
|
||||
// TODO: parser should read from io.Reader? Or at the very least, make it
|
||||
// read from []byte rather than string
|
||||
data, err := ioutil.ReadAll(dec.r)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
@@ -121,11 +147,15 @@ func (dec *Decoder) Decode(v interface{}) (MetaData, error) {
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
|
||||
md := MetaData{
|
||||
p.mapping, p.types, p.ordered,
|
||||
make(map[string]bool, len(p.ordered)), nil,
|
||||
mapping: p.mapping,
|
||||
types: p.types,
|
||||
keys: p.ordered,
|
||||
decoded: make(map[string]struct{}, len(p.ordered)),
|
||||
context: nil,
|
||||
}
|
||||
return md, md.unify(p.mapping, indirect(rv))
|
||||
return md, md.unify(p.mapping, rv)
|
||||
}
|
||||
|
||||
// Decode the TOML data in to the pointer v.
|
||||
@@ -218,9 +248,7 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
||||
return e("unsupported type %s", rv.Type())
|
||||
}
|
||||
return md.unifyAnything(data, rv)
|
||||
case reflect.Float32:
|
||||
fallthrough
|
||||
case reflect.Float64:
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return md.unifyFloat64(data, rv)
|
||||
}
|
||||
return e("unsupported type %s", rv.Kind())
|
||||
@@ -254,17 +282,17 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
||||
for _, i := range f.index {
|
||||
subv = indirect(subv.Field(i))
|
||||
}
|
||||
|
||||
if isUnifiable(subv) {
|
||||
md.decoded[md.context.add(key).String()] = true
|
||||
md.decoded[md.context.add(key).String()] = struct{}{}
|
||||
md.context = append(md.context, key)
|
||||
if err := md.unify(datum, subv); err != nil {
|
||||
err := md.unify(datum, subv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
} else if f.name != "" {
|
||||
// Bad user! No soup for you!
|
||||
return e("cannot write unexported field %s.%s",
|
||||
rv.Type().String(), f.name)
|
||||
return e("cannot write unexported field %s.%s", rv.Type().String(), f.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,22 +311,22 @@ func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
||||
if tmap == nil {
|
||||
return nil
|
||||
}
|
||||
return badtype("map", mapping)
|
||||
return md.badtype("map", mapping)
|
||||
}
|
||||
if rv.IsNil() {
|
||||
rv.Set(reflect.MakeMap(rv.Type()))
|
||||
}
|
||||
for k, v := range tmap {
|
||||
md.decoded[md.context.add(k).String()] = true
|
||||
md.decoded[md.context.add(k).String()] = struct{}{}
|
||||
md.context = append(md.context, k)
|
||||
|
||||
rvkey := indirect(reflect.New(rv.Type().Key()))
|
||||
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
|
||||
if err := md.unify(v, rvval); err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
|
||||
rvkey := indirect(reflect.New(rv.Type().Key()))
|
||||
rvkey.SetString(k)
|
||||
rv.SetMapIndex(rvkey, rvval)
|
||||
}
|
||||
@@ -311,7 +339,7 @@ func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
||||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return badtype("slice", data)
|
||||
return md.badtype("slice", data)
|
||||
}
|
||||
if l := datav.Len(); l != rv.Len() {
|
||||
return e("expected array length %d; got TOML array of length %d", rv.Len(), l)
|
||||
@@ -325,7 +353,7 @@ func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
||||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return badtype("slice", data)
|
||||
return md.badtype("slice", data)
|
||||
}
|
||||
n := datav.Len()
|
||||
if rv.IsNil() || rv.Cap() < n {
|
||||
@@ -346,26 +374,21 @@ func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error {
|
||||
if _, ok := data.(time.Time); ok {
|
||||
rv.Set(reflect.ValueOf(data))
|
||||
return nil
|
||||
}
|
||||
return badtype("time.Time", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
|
||||
if s, ok := data.(string); ok {
|
||||
rv.SetString(s)
|
||||
return nil
|
||||
}
|
||||
return badtype("string", data)
|
||||
return md.badtype("string", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
||||
if num, ok := data.(float64); ok {
|
||||
switch rv.Kind() {
|
||||
case reflect.Float32:
|
||||
if num < -math.MaxFloat32 || num > math.MaxFloat32 {
|
||||
return e("value %f is out of range for float32", num)
|
||||
}
|
||||
fallthrough
|
||||
case reflect.Float64:
|
||||
rv.SetFloat(num)
|
||||
@@ -374,7 +397,26 @@ func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return badtype("float", data)
|
||||
|
||||
if num, ok := data.(int64); ok {
|
||||
switch rv.Kind() {
|
||||
case reflect.Float32:
|
||||
if num < -maxSafeFloat32Int || num > maxSafeFloat32Int {
|
||||
return e("value %d is out of range for float32", num)
|
||||
}
|
||||
fallthrough
|
||||
case reflect.Float64:
|
||||
if num < -maxSafeFloat64Int || num > maxSafeFloat64Int {
|
||||
return e("value %d is out of range for float64", num)
|
||||
}
|
||||
rv.SetFloat(float64(num))
|
||||
default:
|
||||
panic("bug")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return md.badtype("float", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
||||
@@ -421,7 +463,7 @@ func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return badtype("integer", data)
|
||||
return md.badtype("integer", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
||||
@@ -429,7 +471,7 @@ func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
||||
rv.SetBool(b)
|
||||
return nil
|
||||
}
|
||||
return badtype("boolean", data)
|
||||
return md.badtype("boolean", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
||||
@@ -440,6 +482,12 @@ func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
||||
func (md *MetaData) unifyText(data interface{}, v encoding.TextUnmarshaler) error {
|
||||
var s string
|
||||
switch sdata := data.(type) {
|
||||
case Marshaler:
|
||||
text, err := sdata.MarshalTOML()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s = string(text)
|
||||
case TextMarshaler:
|
||||
text, err := sdata.MarshalText()
|
||||
if err != nil {
|
||||
@@ -457,7 +505,7 @@ func (md *MetaData) unifyText(data interface{}, v encoding.TextUnmarshaler) erro
|
||||
case float64:
|
||||
s = fmt.Sprintf("%f", sdata)
|
||||
default:
|
||||
return badtype("primitive (string-like)", data)
|
||||
return md.badtype("primitive (string-like)", data)
|
||||
}
|
||||
if err := v.UnmarshalText([]byte(s)); err != nil {
|
||||
return err
|
||||
@@ -465,17 +513,22 @@ func (md *MetaData) unifyText(data interface{}, v encoding.TextUnmarshaler) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) badtype(dst string, data interface{}) error {
|
||||
return e("incompatible types: TOML key %q has type %T; destination has type %s", md.context, data, dst)
|
||||
}
|
||||
|
||||
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
|
||||
func rvalue(v interface{}) reflect.Value {
|
||||
return indirect(reflect.ValueOf(v))
|
||||
}
|
||||
|
||||
// indirect returns the value pointed to by a pointer.
|
||||
// Pointers are followed until the value is not a pointer.
|
||||
// New values are allocated for each nil pointer.
|
||||
//
|
||||
// An exception to this rule is if the value satisfies an interface of
|
||||
// interest to us (like encoding.TextUnmarshaler).
|
||||
// Pointers are followed until the value is not a pointer. New values are
|
||||
// allocated for each nil pointer.
|
||||
//
|
||||
// An exception to this rule is if the value satisfies an interface of interest
|
||||
// to us (like encoding.TextUnmarshaler).
|
||||
func indirect(v reflect.Value) reflect.Value {
|
||||
if v.Kind() != reflect.Ptr {
|
||||
if v.CanSet() {
|
||||
@@ -505,7 +558,3 @@ func isUnifiable(rv reflect.Value) bool {
|
||||
func e(format string, args ...interface{}) error {
|
||||
return fmt.Errorf("toml: "+format, args...)
|
||||
}
|
||||
|
||||
func badtype(expected string, data interface{}) error {
|
||||
return e("cannot load TOML value of type %T into a Go %s", data, expected)
|
||||
}
|
||||
|
||||
1
vendor/github.com/BurntSushi/toml/decode_go116.go
generated
vendored
1
vendor/github.com/BurntSushi/toml/decode_go116.go
generated
vendored
@@ -1,3 +1,4 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
package toml
|
||||
|
||||
24
vendor/github.com/BurntSushi/toml/deprecated.go
generated
vendored
24
vendor/github.com/BurntSushi/toml/deprecated.go
generated
vendored
@@ -5,29 +5,17 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// DEPRECATED!
|
||||
//
|
||||
// Use the identical encoding.TextMarshaler instead. It is defined here to
|
||||
// support Go 1.1 and older.
|
||||
// Deprecated: use encoding.TextMarshaler
|
||||
type TextMarshaler encoding.TextMarshaler
|
||||
|
||||
// DEPRECATED!
|
||||
//
|
||||
// Use the identical encoding.TextUnmarshaler instead. It is defined here to
|
||||
// support Go 1.1 and older.
|
||||
// Deprecated: use encoding.TextUnmarshaler
|
||||
type TextUnmarshaler encoding.TextUnmarshaler
|
||||
|
||||
// DEPRECATED!
|
||||
//
|
||||
// Use MetaData.PrimitiveDecode instead.
|
||||
// Deprecated: use MetaData.PrimitiveDecode.
|
||||
func PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||
md := MetaData{decoded: make(map[string]bool)}
|
||||
md := MetaData{decoded: make(map[string]struct{})}
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
||||
|
||||
// DEPRECATED!
|
||||
//
|
||||
// Use NewDecoder(reader).Decode(&v) instead.
|
||||
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
|
||||
return NewDecoder(r).Decode(v)
|
||||
}
|
||||
// Deprecated: use NewDecoder(reader).Decode(&value).
|
||||
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { return NewDecoder(r).Decode(v) }
|
||||
|
||||
150
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
150
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
@@ -21,12 +21,11 @@ type tomlEncodeError struct{ error }
|
||||
var (
|
||||
errArrayNilElement = errors.New("toml: cannot encode array with nil element")
|
||||
errNonString = errors.New("toml: cannot encode a map with non-string key type")
|
||||
errAnonNonStruct = errors.New("toml: cannot encode an anonymous field that is not a struct")
|
||||
errNoKey = errors.New("toml: top-level values must be Go maps or structs")
|
||||
errAnything = errors.New("") // used in testing
|
||||
)
|
||||
|
||||
var quotedReplacer = strings.NewReplacer(
|
||||
var dblQuotedReplacer = strings.NewReplacer(
|
||||
"\"", "\\\"",
|
||||
"\\", "\\\\",
|
||||
"\x00", `\u0000`,
|
||||
@@ -64,13 +63,22 @@ var quotedReplacer = strings.NewReplacer(
|
||||
"\x7f", `\u007f`,
|
||||
)
|
||||
|
||||
// Marshaler is the interface implemented by types that can marshal themselves
|
||||
// into valid TOML.
|
||||
type Marshaler interface {
|
||||
MarshalTOML() ([]byte, error)
|
||||
}
|
||||
|
||||
// Encoder encodes a Go to a TOML document.
|
||||
//
|
||||
// The mapping between Go values and TOML values should be precisely the same as
|
||||
// for the Decode* functions. Similarly, the TextMarshaler interface is
|
||||
// supported by encoding the resulting bytes as strings. If you want to write
|
||||
// arbitrary binary data then you will need to use something like base64 since
|
||||
// TOML does not have any binary types.
|
||||
// for the Decode* functions.
|
||||
//
|
||||
// The toml.Marshaler and encoder.TextMarshaler interfaces are supported to
|
||||
// encoding the value as custom TOML.
|
||||
//
|
||||
// If you want to write arbitrary binary data then you will need to use
|
||||
// something like base64 since TOML does not have any binary types.
|
||||
//
|
||||
// When encoding TOML hashes (Go maps or structs), keys without any sub-hashes
|
||||
// are encoded first.
|
||||
@@ -83,16 +91,14 @@ var quotedReplacer = strings.NewReplacer(
|
||||
// structs. (e.g. [][]map[string]string is not allowed but []map[string]string
|
||||
// is okay, as is []map[string][]string).
|
||||
//
|
||||
// NOTE: Only exported keys are encoded due to the use of reflection. Unexported
|
||||
// NOTE: only exported keys are encoded due to the use of reflection. Unexported
|
||||
// keys are silently discarded.
|
||||
type Encoder struct {
|
||||
// The string to use for a single indentation level. The default is two
|
||||
// spaces.
|
||||
// String to use for a single indentation level; default is two spaces.
|
||||
Indent string
|
||||
|
||||
// hasWritten is whether we have written any output to w yet.
|
||||
hasWritten bool
|
||||
w *bufio.Writer
|
||||
hasWritten bool // written any output to w yet?
|
||||
}
|
||||
|
||||
// NewEncoder create a new Encoder.
|
||||
@@ -130,12 +136,13 @@ func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
|
||||
}
|
||||
|
||||
func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
||||
// Special case. Time needs to be in ISO8601 format.
|
||||
// Special case. If we can marshal the type to text, then we used that.
|
||||
// Basically, this prevents the encoder for handling these types as
|
||||
// generic structs (or whatever the underlying type of a TextMarshaler is).
|
||||
// Special case: time needs to be in ISO8601 format.
|
||||
//
|
||||
// Special case: if we can marshal the type to text, then we used that. This
|
||||
// prevents the encoder for handling these types as generic structs (or
|
||||
// whatever the underlying type of a TextMarshaler is).
|
||||
switch t := rv.Interface().(type) {
|
||||
case time.Time, encoding.TextMarshaler:
|
||||
case time.Time, encoding.TextMarshaler, Marshaler:
|
||||
enc.writeKeyValue(key, rv, false)
|
||||
return
|
||||
// TODO: #76 would make this superfluous after implemented.
|
||||
@@ -200,13 +207,19 @@ func (enc *Encoder) eElement(rv reflect.Value) {
|
||||
enc.wf(v.In(time.UTC).Format(format))
|
||||
}
|
||||
return
|
||||
case encoding.TextMarshaler:
|
||||
// Use text marshaler if it's available for this value.
|
||||
if s, err := v.MarshalText(); err != nil {
|
||||
case Marshaler:
|
||||
s, err := v.MarshalTOML()
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
} else {
|
||||
enc.writeQuoted(string(s))
|
||||
}
|
||||
enc.writeQuoted(string(s))
|
||||
return
|
||||
case encoding.TextMarshaler:
|
||||
s, err := v.MarshalText()
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
enc.writeQuoted(string(s))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -260,7 +273,7 @@ func floatAddDecimal(fstr string) string {
|
||||
}
|
||||
|
||||
func (enc *Encoder) writeQuoted(s string) {
|
||||
enc.wf("\"%s\"", quotedReplacer.Replace(s))
|
||||
enc.wf("\"%s\"", dblQuotedReplacer.Replace(s))
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
|
||||
@@ -286,7 +299,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
||||
continue
|
||||
}
|
||||
enc.newline()
|
||||
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
|
||||
enc.wf("%s[[%s]]", enc.indentStr(key), key)
|
||||
enc.newline()
|
||||
enc.eMapOrStruct(key, trv, false)
|
||||
}
|
||||
@@ -299,7 +312,7 @@ func (enc *Encoder) eTable(key Key, rv reflect.Value) {
|
||||
enc.newline()
|
||||
}
|
||||
if len(key) > 0 {
|
||||
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
|
||||
enc.wf("%s[%s]", enc.indentStr(key), key)
|
||||
enc.newline()
|
||||
}
|
||||
enc.eMapOrStruct(key, rv, false)
|
||||
@@ -328,7 +341,7 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
|
||||
var mapKeysDirect, mapKeysSub []string
|
||||
for _, mapKey := range rv.MapKeys() {
|
||||
k := mapKey.String()
|
||||
if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
|
||||
if typeIsTable(tomlTypeOfGo(rv.MapIndex(mapKey))) {
|
||||
mapKeysSub = append(mapKeysSub, k)
|
||||
} else {
|
||||
mapKeysDirect = append(mapKeysDirect, k)
|
||||
@@ -364,6 +377,8 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
|
||||
}
|
||||
}
|
||||
|
||||
const is32Bit = (32 << (^uint(0) >> 63)) == 32
|
||||
|
||||
func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
|
||||
// Write keys for fields directly under this key first, because if we write
|
||||
// a field that creates a new table then all keys under it will be in that
|
||||
@@ -408,10 +423,20 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if typeIsHash(tomlTypeOfGo(frv)) {
|
||||
if typeIsTable(tomlTypeOfGo(frv)) {
|
||||
fieldsSub = append(fieldsSub, append(start, f.Index...))
|
||||
} else {
|
||||
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
||||
// Copy so it works correct on 32bit archs; not clear why this
|
||||
// is needed. See #314, and https://www.reddit.com/r/golang/comments/pnx8v4
|
||||
// This also works fine on 64bit, but 32bit archs are somewhat
|
||||
// rare and this is a wee bit faster.
|
||||
if is32Bit {
|
||||
copyStart := make([]int, len(start))
|
||||
copy(copyStart, start)
|
||||
fieldsDirect = append(fieldsDirect, append(copyStart, f.Index...))
|
||||
} else {
|
||||
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -462,13 +487,13 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// tomlTypeName returns the TOML type name of the Go value's type. It is
|
||||
// used to determine whether the types of array elements are mixed (which is
|
||||
// forbidden). If the Go value is nil, then it is illegal for it to be an array
|
||||
// element, and valueIsNil is returned as true.
|
||||
|
||||
// Returns the TOML type of a Go value. The type may be `nil`, which means
|
||||
// no concrete TOML type could be found.
|
||||
// tomlTypeOfGo returns the TOML type name of the Go value's type.
|
||||
//
|
||||
// It is used to determine whether the types of array elements are mixed (which
|
||||
// is forbidden). If the Go value is nil, then it is illegal for it to be an
|
||||
// array element, and valueIsNil is returned as true.
|
||||
//
|
||||
// The type may be `nil`, which means no concrete TOML type could be found.
|
||||
func tomlTypeOfGo(rv reflect.Value) tomlType {
|
||||
if isNil(rv) || !rv.IsValid() {
|
||||
return nil
|
||||
@@ -495,32 +520,43 @@ func tomlTypeOfGo(rv reflect.Value) tomlType {
|
||||
case reflect.Map:
|
||||
return tomlHash
|
||||
case reflect.Struct:
|
||||
switch rv.Interface().(type) {
|
||||
case time.Time:
|
||||
if _, ok := rv.Interface().(time.Time); ok {
|
||||
return tomlDatetime
|
||||
case encoding.TextMarshaler:
|
||||
return tomlString
|
||||
default:
|
||||
// Someone used a pointer receiver: we can make it work for pointer
|
||||
// values.
|
||||
if rv.CanAddr() {
|
||||
_, ok := rv.Addr().Interface().(encoding.TextMarshaler)
|
||||
if ok {
|
||||
return tomlString
|
||||
}
|
||||
}
|
||||
return tomlHash
|
||||
}
|
||||
if isMarshaler(rv) {
|
||||
return tomlString
|
||||
}
|
||||
return tomlHash
|
||||
default:
|
||||
_, ok := rv.Interface().(encoding.TextMarshaler)
|
||||
if ok {
|
||||
if isMarshaler(rv) {
|
||||
return tomlString
|
||||
}
|
||||
|
||||
encPanic(errors.New("unsupported type: " + rv.Kind().String()))
|
||||
panic("") // Need *some* return value
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func isMarshaler(rv reflect.Value) bool {
|
||||
switch rv.Interface().(type) {
|
||||
case encoding.TextMarshaler:
|
||||
return true
|
||||
case Marshaler:
|
||||
return true
|
||||
}
|
||||
|
||||
// Someone used a pointer receiver: we can make it work for pointer values.
|
||||
if rv.CanAddr() {
|
||||
if _, ok := rv.Addr().Interface().(encoding.TextMarshaler); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rv.Addr().Interface().(Marshaler); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// tomlArrayType returns the element type of a TOML array. The type returned
|
||||
// may be nil if it cannot be determined (e.g., a nil slice or a zero length
|
||||
// slize). This function may also panic if it finds a type that cannot be
|
||||
@@ -604,7 +640,14 @@ func (enc *Encoder) newline() {
|
||||
//
|
||||
// key = <any value>
|
||||
//
|
||||
// If inline is true it won't add a newline at the end.
|
||||
// This is also used for "k = v" in inline tables; so something like this will
|
||||
// be written in three calls:
|
||||
//
|
||||
// ┌────────────────────┐
|
||||
// │ ┌───┐ ┌─────┐│
|
||||
// v v v v vv
|
||||
// key = {k = v, k2 = v2}
|
||||
//
|
||||
func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
|
||||
if len(key) == 0 {
|
||||
encPanic(errNoKey)
|
||||
@@ -617,7 +660,8 @@ func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
|
||||
}
|
||||
|
||||
func (enc *Encoder) wf(format string, v ...interface{}) {
|
||||
if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
|
||||
_, err := fmt.Fprintf(enc.w, format, v...)
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
enc.hasWritten = true
|
||||
|
||||
229
vendor/github.com/BurntSushi/toml/error.go
generated
vendored
Normal file
229
vendor/github.com/BurntSushi/toml/error.go
generated
vendored
Normal file
@@ -0,0 +1,229 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseError is returned when there is an error parsing the TOML syntax.
|
||||
//
|
||||
// For example invalid syntax, duplicate keys, etc.
|
||||
//
|
||||
// In addition to the error message itself, you can also print detailed location
|
||||
// information with context by using ErrorWithLocation():
|
||||
//
|
||||
// toml: error: Key 'fruit' was already created and cannot be used as an array.
|
||||
//
|
||||
// At line 4, column 2-7:
|
||||
//
|
||||
// 2 | fruit = []
|
||||
// 3 |
|
||||
// 4 | [[fruit]] # Not allowed
|
||||
// ^^^^^
|
||||
//
|
||||
// Furthermore, the ErrorWithUsage() can be used to print the above with some
|
||||
// more detailed usage guidance:
|
||||
//
|
||||
// toml: error: newlines not allowed within inline tables
|
||||
//
|
||||
// At line 1, column 18:
|
||||
//
|
||||
// 1 | x = [{ key = 42 #
|
||||
// ^
|
||||
//
|
||||
// Error help:
|
||||
//
|
||||
// Inline tables must always be on a single line:
|
||||
//
|
||||
// table = {key = 42, second = 43}
|
||||
//
|
||||
// It is invalid to split them over multiple lines like so:
|
||||
//
|
||||
// # INVALID
|
||||
// table = {
|
||||
// key = 42,
|
||||
// second = 43
|
||||
// }
|
||||
//
|
||||
// Use regular for this:
|
||||
//
|
||||
// [table]
|
||||
// key = 42
|
||||
// second = 43
|
||||
type ParseError struct {
|
||||
Message string // Short technical message.
|
||||
Usage string // Longer message with usage guidance; may be blank.
|
||||
Position Position // Position of the error
|
||||
LastKey string // Last parsed key, may be blank.
|
||||
Line int // Line the error occurred. Deprecated: use Position.
|
||||
|
||||
err error
|
||||
input string
|
||||
}
|
||||
|
||||
// Position of an error.
|
||||
type Position struct {
|
||||
Line int // Line number, starting at 1.
|
||||
Start int // Start of error, as byte offset starting at 0.
|
||||
Len int // Lenght in bytes.
|
||||
}
|
||||
|
||||
func (pe ParseError) Error() string {
|
||||
msg := pe.Message
|
||||
if msg == "" { // Error from errorf()
|
||||
msg = pe.err.Error()
|
||||
}
|
||||
|
||||
if pe.LastKey == "" {
|
||||
return fmt.Sprintf("toml: line %d: %s", pe.Position.Line, msg)
|
||||
}
|
||||
return fmt.Sprintf("toml: line %d (last key %q): %s",
|
||||
pe.Position.Line, pe.LastKey, msg)
|
||||
}
|
||||
|
||||
// ErrorWithUsage() returns the error with detailed location context.
|
||||
//
|
||||
// See the documentation on ParseError.
|
||||
func (pe ParseError) ErrorWithPosition() string {
|
||||
if pe.input == "" { // Should never happen, but just in case.
|
||||
return pe.Error()
|
||||
}
|
||||
|
||||
var (
|
||||
lines = strings.Split(pe.input, "\n")
|
||||
col = pe.column(lines)
|
||||
b = new(strings.Builder)
|
||||
)
|
||||
|
||||
msg := pe.Message
|
||||
if msg == "" {
|
||||
msg = pe.err.Error()
|
||||
}
|
||||
|
||||
// TODO: don't show control characters as literals? This may not show up
|
||||
// well everywhere.
|
||||
|
||||
if pe.Position.Len == 1 {
|
||||
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n",
|
||||
msg, pe.Position.Line, col+1)
|
||||
} else {
|
||||
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n",
|
||||
msg, pe.Position.Line, col, col+pe.Position.Len)
|
||||
}
|
||||
if pe.Position.Line > 2 {
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, lines[pe.Position.Line-3])
|
||||
}
|
||||
if pe.Position.Line > 1 {
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, lines[pe.Position.Line-2])
|
||||
}
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, lines[pe.Position.Line-1])
|
||||
fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col), strings.Repeat("^", pe.Position.Len))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ErrorWithUsage() returns the error with detailed location context and usage
|
||||
// guidance.
|
||||
//
|
||||
// See the documentation on ParseError.
|
||||
func (pe ParseError) ErrorWithUsage() string {
|
||||
m := pe.ErrorWithPosition()
|
||||
if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" {
|
||||
return m + "Error help:\n\n " +
|
||||
strings.ReplaceAll(strings.TrimSpace(u.Usage()), "\n", "\n ") +
|
||||
"\n"
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (pe ParseError) column(lines []string) int {
|
||||
var pos, col int
|
||||
for i := range lines {
|
||||
ll := len(lines[i]) + 1 // +1 for the removed newline
|
||||
if pos+ll >= pe.Position.Start {
|
||||
col = pe.Position.Start - pos
|
||||
if col < 0 { // Should never happen, but just in case.
|
||||
col = 0
|
||||
}
|
||||
break
|
||||
}
|
||||
pos += ll
|
||||
}
|
||||
|
||||
return col
|
||||
}
|
||||
|
||||
type (
|
||||
errLexControl struct{ r rune }
|
||||
errLexEscape struct{ r rune }
|
||||
errLexUTF8 struct{ b byte }
|
||||
errLexInvalidNum struct{ v string }
|
||||
errLexInvalidDate struct{ v string }
|
||||
errLexInlineTableNL struct{}
|
||||
errLexStringNL struct{}
|
||||
)
|
||||
|
||||
func (e errLexControl) Error() string {
|
||||
return fmt.Sprintf("TOML files cannot contain control characters: '0x%02x'", e.r)
|
||||
}
|
||||
func (e errLexControl) Usage() string { return "" }
|
||||
|
||||
func (e errLexEscape) Error() string { return fmt.Sprintf(`invalid escape in string '\%c'`, e.r) }
|
||||
func (e errLexEscape) Usage() string { return usageEscape }
|
||||
func (e errLexUTF8) Error() string { return fmt.Sprintf("invalid UTF-8 byte: 0x%02x", e.b) }
|
||||
func (e errLexUTF8) Usage() string { return "" }
|
||||
func (e errLexInvalidNum) Error() string { return fmt.Sprintf("invalid number: %q", e.v) }
|
||||
func (e errLexInvalidNum) Usage() string { return "" }
|
||||
func (e errLexInvalidDate) Error() string { return fmt.Sprintf("invalid date: %q", e.v) }
|
||||
func (e errLexInvalidDate) Usage() string { return "" }
|
||||
func (e errLexInlineTableNL) Error() string { return "newlines not allowed within inline tables" }
|
||||
func (e errLexInlineTableNL) Usage() string { return usageInlineNewline }
|
||||
func (e errLexStringNL) Error() string { return "strings cannot contain newlines" }
|
||||
func (e errLexStringNL) Usage() string { return usageStringNewline }
|
||||
|
||||
const usageEscape = `
|
||||
A '\' inside a "-delimited string is interpreted as an escape character.
|
||||
|
||||
The following escape sequences are supported:
|
||||
\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX
|
||||
|
||||
To prevent a '\' from being recognized as an escape character, use either:
|
||||
|
||||
- a ' or '''-delimited string; escape characters aren't processed in them; or
|
||||
- write two backslashes to get a single backslash: '\\'.
|
||||
|
||||
If you're trying to add a Windows path (e.g. "C:\Users\martin") then using '/'
|
||||
instead of '\' will usually also work: "C:/Users/martin".
|
||||
`
|
||||
|
||||
const usageInlineNewline = `
|
||||
Inline tables must always be on a single line:
|
||||
|
||||
table = {key = 42, second = 43}
|
||||
|
||||
It is invalid to split them over multiple lines like so:
|
||||
|
||||
# INVALID
|
||||
table = {
|
||||
key = 42,
|
||||
second = 43
|
||||
}
|
||||
|
||||
Use regular for this:
|
||||
|
||||
[table]
|
||||
key = 42
|
||||
second = 43
|
||||
`
|
||||
|
||||
const usageStringNewline = `
|
||||
Strings must always be on a single line, and cannot span more than one line:
|
||||
|
||||
# INVALID
|
||||
string = "Hello,
|
||||
world!"
|
||||
|
||||
Instead use """ or ''' to split strings over multiple lines:
|
||||
|
||||
string = """Hello,
|
||||
world!"""
|
||||
`
|
||||
0
vendor/github.com/BurntSushi/toml/go.sum
generated
vendored
0
vendor/github.com/BurntSushi/toml/go.sum
generated
vendored
354
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
354
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
@@ -37,28 +37,14 @@ const (
|
||||
itemInlineTableEnd
|
||||
)
|
||||
|
||||
const (
|
||||
eof = 0
|
||||
comma = ','
|
||||
tableStart = '['
|
||||
tableEnd = ']'
|
||||
arrayTableStart = '['
|
||||
arrayTableEnd = ']'
|
||||
tableSep = '.'
|
||||
keySep = '='
|
||||
arrayStart = '['
|
||||
arrayEnd = ']'
|
||||
commentStart = '#'
|
||||
stringStart = '"'
|
||||
stringEnd = '"'
|
||||
rawStringStart = '\''
|
||||
rawStringEnd = '\''
|
||||
inlineTableStart = '{'
|
||||
inlineTableEnd = '}'
|
||||
)
|
||||
const eof = 0
|
||||
|
||||
type stateFn func(lx *lexer) stateFn
|
||||
|
||||
func (p Position) String() string {
|
||||
return fmt.Sprintf("at line %d; start %d; length %d", p.Line, p.Start, p.Len)
|
||||
}
|
||||
|
||||
type lexer struct {
|
||||
input string
|
||||
start int
|
||||
@@ -67,26 +53,26 @@ type lexer struct {
|
||||
state stateFn
|
||||
items chan item
|
||||
|
||||
// Allow for backing up up to four runes.
|
||||
// This is necessary because TOML contains 3-rune tokens (""" and ''').
|
||||
// Allow for backing up up to 4 runes. This is necessary because TOML
|
||||
// contains 3-rune tokens (""" and ''').
|
||||
prevWidths [4]int
|
||||
nprev int // how many of prevWidths are in use
|
||||
// If we emit an eof, we can still back up, but it is not OK to call
|
||||
// next again.
|
||||
atEOF bool
|
||||
nprev int // how many of prevWidths are in use
|
||||
atEOF bool // If we emit an eof, we can still back up, but it is not OK to call next again.
|
||||
|
||||
// A stack of state functions used to maintain context.
|
||||
// The idea is to reuse parts of the state machine in various places.
|
||||
// For example, values can appear at the top level or within arbitrarily
|
||||
// nested arrays. The last state on the stack is used after a value has
|
||||
// been lexed. Similarly for comments.
|
||||
//
|
||||
// The idea is to reuse parts of the state machine in various places. For
|
||||
// example, values can appear at the top level or within arbitrarily nested
|
||||
// arrays. The last state on the stack is used after a value has been lexed.
|
||||
// Similarly for comments.
|
||||
stack []stateFn
|
||||
}
|
||||
|
||||
type item struct {
|
||||
typ itemType
|
||||
val string
|
||||
line int
|
||||
typ itemType
|
||||
val string
|
||||
err error
|
||||
pos Position
|
||||
}
|
||||
|
||||
func (lx *lexer) nextItem() item {
|
||||
@@ -96,7 +82,7 @@ func (lx *lexer) nextItem() item {
|
||||
return item
|
||||
default:
|
||||
lx.state = lx.state(lx)
|
||||
//fmt.Printf(" STATE %-24s current: %-10q stack: %s\n", lx.state, lx.current(), lx.stack)
|
||||
//fmt.Printf(" STATE %-24s current: %-10q stack: %s\n", lx.state, lx.current(), lx.stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,9 +91,9 @@ func lex(input string) *lexer {
|
||||
lx := &lexer{
|
||||
input: input,
|
||||
state: lexTop,
|
||||
line: 1,
|
||||
items: make(chan item, 10),
|
||||
stack: make([]stateFn, 0, 10),
|
||||
line: 1,
|
||||
}
|
||||
return lx
|
||||
}
|
||||
@@ -129,13 +115,25 @@ func (lx *lexer) current() string {
|
||||
return lx.input[lx.start:lx.pos]
|
||||
}
|
||||
|
||||
func (lx lexer) getPos() Position {
|
||||
p := Position{
|
||||
Line: lx.line,
|
||||
Start: lx.start,
|
||||
Len: lx.pos - lx.start,
|
||||
}
|
||||
if p.Len <= 0 {
|
||||
p.Len = 1
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (lx *lexer) emit(typ itemType) {
|
||||
lx.items <- item{typ, lx.current(), lx.line}
|
||||
lx.items <- item{typ: typ, pos: lx.getPos(), val: lx.current()}
|
||||
lx.start = lx.pos
|
||||
}
|
||||
|
||||
func (lx *lexer) emitTrim(typ itemType) {
|
||||
lx.items <- item{typ, strings.TrimSpace(lx.current()), lx.line}
|
||||
lx.items <- item{typ: typ, pos: lx.getPos(), val: strings.TrimSpace(lx.current())}
|
||||
lx.start = lx.pos
|
||||
}
|
||||
|
||||
@@ -160,7 +158,13 @@ func (lx *lexer) next() (r rune) {
|
||||
|
||||
r, w := utf8.DecodeRuneInString(lx.input[lx.pos:])
|
||||
if r == utf8.RuneError {
|
||||
lx.errorf("invalid UTF-8 byte at position %d (line %d): 0x%02x", lx.pos, lx.line, lx.input[lx.pos])
|
||||
lx.error(errLexUTF8{lx.input[lx.pos]})
|
||||
return utf8.RuneError
|
||||
}
|
||||
|
||||
// Note: don't use peek() here, as this calls next().
|
||||
if isControl(r) || (r == '\r' && (len(lx.input)-1 == lx.pos || lx.input[lx.pos+1] != '\n')) {
|
||||
lx.errorControlChar(r)
|
||||
return utf8.RuneError
|
||||
}
|
||||
|
||||
@@ -188,6 +192,7 @@ func (lx *lexer) backup() {
|
||||
lx.prevWidths[1] = lx.prevWidths[2]
|
||||
lx.prevWidths[2] = lx.prevWidths[3]
|
||||
lx.nprev--
|
||||
|
||||
lx.pos -= w
|
||||
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
|
||||
lx.line--
|
||||
@@ -223,18 +228,58 @@ func (lx *lexer) skip(pred func(rune) bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// errorf stops all lexing by emitting an error and returning `nil`.
|
||||
// error stops all lexing by emitting an error and returning `nil`.
|
||||
//
|
||||
// Note that any value that is a character is escaped if it's a special
|
||||
// character (newlines, tabs, etc.).
|
||||
func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
|
||||
lx.items <- item{
|
||||
itemError,
|
||||
fmt.Sprintf(format, values...),
|
||||
lx.line,
|
||||
func (lx *lexer) error(err error) stateFn {
|
||||
if lx.atEOF {
|
||||
return lx.errorPrevLine(err)
|
||||
}
|
||||
lx.items <- item{typ: itemError, pos: lx.getPos(), err: err}
|
||||
return nil
|
||||
}
|
||||
|
||||
// errorfPrevline is like error(), but sets the position to the last column of
|
||||
// the previous line.
|
||||
//
|
||||
// This is so that unexpected EOF or NL errors don't show on a new blank line.
|
||||
func (lx *lexer) errorPrevLine(err error) stateFn {
|
||||
pos := lx.getPos()
|
||||
pos.Line--
|
||||
pos.Len = 1
|
||||
pos.Start = lx.pos - 1
|
||||
lx.items <- item{typ: itemError, pos: pos, err: err}
|
||||
return nil
|
||||
}
|
||||
|
||||
// errorPos is like error(), but allows explicitly setting the position.
|
||||
func (lx *lexer) errorPos(start, length int, err error) stateFn {
|
||||
pos := lx.getPos()
|
||||
pos.Start = start
|
||||
pos.Len = length
|
||||
lx.items <- item{typ: itemError, pos: pos, err: err}
|
||||
return nil
|
||||
}
|
||||
|
||||
// errorf is like error, and creates a new error.
|
||||
func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
|
||||
if lx.atEOF {
|
||||
pos := lx.getPos()
|
||||
pos.Line--
|
||||
pos.Len = 1
|
||||
pos.Start = lx.pos - 1
|
||||
lx.items <- item{typ: itemError, pos: pos, err: fmt.Errorf(format, values...)}
|
||||
return nil
|
||||
}
|
||||
lx.items <- item{typ: itemError, pos: lx.getPos(), err: fmt.Errorf(format, values...)}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lx *lexer) errorControlChar(cc rune) stateFn {
|
||||
return lx.errorPos(lx.pos-1, 1, errLexControl{cc})
|
||||
}
|
||||
|
||||
// lexTop consumes elements at the top level of TOML data.
|
||||
func lexTop(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
@@ -242,10 +287,10 @@ func lexTop(lx *lexer) stateFn {
|
||||
return lexSkip(lx, lexTop)
|
||||
}
|
||||
switch r {
|
||||
case commentStart:
|
||||
case '#':
|
||||
lx.push(lexTop)
|
||||
return lexCommentStart
|
||||
case tableStart:
|
||||
case '[':
|
||||
return lexTableStart
|
||||
case eof:
|
||||
if lx.pos > lx.start {
|
||||
@@ -268,7 +313,7 @@ func lexTop(lx *lexer) stateFn {
|
||||
func lexTopEnd(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case r == commentStart:
|
||||
case r == '#':
|
||||
// a comment will read to a newline for us.
|
||||
lx.push(lexTop)
|
||||
return lexCommentStart
|
||||
@@ -292,7 +337,7 @@ func lexTopEnd(lx *lexer) stateFn {
|
||||
// It also handles the case that this is an item in an array of tables.
|
||||
// e.g., '[[name]]'.
|
||||
func lexTableStart(lx *lexer) stateFn {
|
||||
if lx.peek() == arrayTableStart {
|
||||
if lx.peek() == '[' {
|
||||
lx.next()
|
||||
lx.emit(itemArrayTableStart)
|
||||
lx.push(lexArrayTableEnd)
|
||||
@@ -309,10 +354,8 @@ func lexTableEnd(lx *lexer) stateFn {
|
||||
}
|
||||
|
||||
func lexArrayTableEnd(lx *lexer) stateFn {
|
||||
if r := lx.next(); r != arrayTableEnd {
|
||||
return lx.errorf(
|
||||
"expected end of table array name delimiter %q, but got %q instead",
|
||||
arrayTableEnd, r)
|
||||
if r := lx.next(); r != ']' {
|
||||
return lx.errorf("expected end of table array name delimiter ']', but got %q instead", r)
|
||||
}
|
||||
lx.emit(itemArrayTableEnd)
|
||||
return lexTopEnd
|
||||
@@ -321,11 +364,11 @@ func lexArrayTableEnd(lx *lexer) stateFn {
|
||||
func lexTableNameStart(lx *lexer) stateFn {
|
||||
lx.skip(isWhitespace)
|
||||
switch r := lx.peek(); {
|
||||
case r == tableEnd || r == eof:
|
||||
case r == ']' || r == eof:
|
||||
return lx.errorf("unexpected end of table name (table names cannot be empty)")
|
||||
case r == tableSep:
|
||||
case r == '.':
|
||||
return lx.errorf("unexpected table separator (table names cannot be empty)")
|
||||
case r == stringStart || r == rawStringStart:
|
||||
case r == '"' || r == '\'':
|
||||
lx.ignore()
|
||||
lx.push(lexTableNameEnd)
|
||||
return lexQuotedName
|
||||
@@ -342,10 +385,10 @@ func lexTableNameEnd(lx *lexer) stateFn {
|
||||
switch r := lx.next(); {
|
||||
case isWhitespace(r):
|
||||
return lexTableNameEnd
|
||||
case r == tableSep:
|
||||
case r == '.':
|
||||
lx.ignore()
|
||||
return lexTableNameStart
|
||||
case r == tableEnd:
|
||||
case r == ']':
|
||||
return lx.pop()
|
||||
default:
|
||||
return lx.errorf("expected '.' or ']' to end table name, but got %q instead", r)
|
||||
@@ -379,10 +422,10 @@ func lexQuotedName(lx *lexer) stateFn {
|
||||
switch {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexValue)
|
||||
case r == stringStart:
|
||||
case r == '"':
|
||||
lx.ignore() // ignore the '"'
|
||||
return lexString
|
||||
case r == rawStringStart:
|
||||
case r == '\'':
|
||||
lx.ignore() // ignore the "'"
|
||||
return lexRawString
|
||||
case r == eof:
|
||||
@@ -400,7 +443,7 @@ func lexKeyStart(lx *lexer) stateFn {
|
||||
return lx.errorf("unexpected '=': key name appears blank")
|
||||
case r == '.':
|
||||
return lx.errorf("unexpected '.': keys cannot start with a '.'")
|
||||
case r == stringStart || r == rawStringStart:
|
||||
case r == '"' || r == '\'':
|
||||
lx.ignore()
|
||||
fallthrough
|
||||
default: // Bare key
|
||||
@@ -416,7 +459,7 @@ func lexKeyNameStart(lx *lexer) stateFn {
|
||||
return lx.errorf("unexpected '='")
|
||||
case r == '.':
|
||||
return lx.errorf("unexpected '.'")
|
||||
case r == stringStart || r == rawStringStart:
|
||||
case r == '"' || r == '\'':
|
||||
lx.ignore()
|
||||
lx.push(lexKeyEnd)
|
||||
return lexQuotedName
|
||||
@@ -434,7 +477,7 @@ func lexKeyEnd(lx *lexer) stateFn {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexKeyEnd)
|
||||
case r == eof:
|
||||
return lx.errorf("unexpected EOF; expected key separator %q", keySep)
|
||||
return lx.errorf("unexpected EOF; expected key separator '='")
|
||||
case r == '.':
|
||||
lx.ignore()
|
||||
return lexKeyNameStart
|
||||
@@ -461,17 +504,17 @@ func lexValue(lx *lexer) stateFn {
|
||||
return lexNumberOrDateStart
|
||||
}
|
||||
switch r {
|
||||
case arrayStart:
|
||||
case '[':
|
||||
lx.ignore()
|
||||
lx.emit(itemArray)
|
||||
return lexArrayValue
|
||||
case inlineTableStart:
|
||||
case '{':
|
||||
lx.ignore()
|
||||
lx.emit(itemInlineTableStart)
|
||||
return lexInlineTableValue
|
||||
case stringStart:
|
||||
if lx.accept(stringStart) {
|
||||
if lx.accept(stringStart) {
|
||||
case '"':
|
||||
if lx.accept('"') {
|
||||
if lx.accept('"') {
|
||||
lx.ignore() // Ignore """
|
||||
return lexMultilineString
|
||||
}
|
||||
@@ -479,9 +522,9 @@ func lexValue(lx *lexer) stateFn {
|
||||
}
|
||||
lx.ignore() // ignore the '"'
|
||||
return lexString
|
||||
case rawStringStart:
|
||||
if lx.accept(rawStringStart) {
|
||||
if lx.accept(rawStringStart) {
|
||||
case '\'':
|
||||
if lx.accept('\'') {
|
||||
if lx.accept('\'') {
|
||||
lx.ignore() // Ignore """
|
||||
return lexMultilineRawString
|
||||
}
|
||||
@@ -520,14 +563,12 @@ func lexArrayValue(lx *lexer) stateFn {
|
||||
switch {
|
||||
case isWhitespace(r) || isNL(r):
|
||||
return lexSkip(lx, lexArrayValue)
|
||||
case r == commentStart:
|
||||
case r == '#':
|
||||
lx.push(lexArrayValue)
|
||||
return lexCommentStart
|
||||
case r == comma:
|
||||
case r == ',':
|
||||
return lx.errorf("unexpected comma")
|
||||
case r == arrayEnd:
|
||||
// NOTE(caleb): The spec isn't clear about whether you can have
|
||||
// a trailing comma or not, so we'll allow it.
|
||||
case r == ']':
|
||||
return lexArrayEnd
|
||||
}
|
||||
|
||||
@@ -540,22 +581,20 @@ func lexArrayValue(lx *lexer) stateFn {
|
||||
// the next value (or the end of the array): it ignores whitespace and newlines
|
||||
// and expects either a ',' or a ']'.
|
||||
func lexArrayValueEnd(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
switch r := lx.next(); {
|
||||
case isWhitespace(r) || isNL(r):
|
||||
return lexSkip(lx, lexArrayValueEnd)
|
||||
case r == commentStart:
|
||||
case r == '#':
|
||||
lx.push(lexArrayValueEnd)
|
||||
return lexCommentStart
|
||||
case r == comma:
|
||||
case r == ',':
|
||||
lx.ignore()
|
||||
return lexArrayValue // move on to the next value
|
||||
case r == arrayEnd:
|
||||
case r == ']':
|
||||
return lexArrayEnd
|
||||
default:
|
||||
return lx.errorf("expected a comma (',') or array terminator (']'), but got %s", runeOrEOF(r))
|
||||
}
|
||||
return lx.errorf(
|
||||
"expected a comma or array terminator %q, but got %s instead",
|
||||
arrayEnd, runeOrEOF(r))
|
||||
}
|
||||
|
||||
// lexArrayEnd finishes the lexing of an array.
|
||||
@@ -574,13 +613,13 @@ func lexInlineTableValue(lx *lexer) stateFn {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexInlineTableValue)
|
||||
case isNL(r):
|
||||
return lx.errorf("newlines not allowed within inline tables")
|
||||
case r == commentStart:
|
||||
return lx.errorPrevLine(errLexInlineTableNL{})
|
||||
case r == '#':
|
||||
lx.push(lexInlineTableValue)
|
||||
return lexCommentStart
|
||||
case r == comma:
|
||||
case r == ',':
|
||||
return lx.errorf("unexpected comma")
|
||||
case r == inlineTableEnd:
|
||||
case r == '}':
|
||||
return lexInlineTableEnd
|
||||
}
|
||||
lx.backup()
|
||||
@@ -596,23 +635,21 @@ func lexInlineTableValueEnd(lx *lexer) stateFn {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexInlineTableValueEnd)
|
||||
case isNL(r):
|
||||
return lx.errorf("newlines not allowed within inline tables")
|
||||
case r == commentStart:
|
||||
return lx.errorPrevLine(errLexInlineTableNL{})
|
||||
case r == '#':
|
||||
lx.push(lexInlineTableValueEnd)
|
||||
return lexCommentStart
|
||||
case r == comma:
|
||||
case r == ',':
|
||||
lx.ignore()
|
||||
lx.skip(isWhitespace)
|
||||
if lx.peek() == '}' {
|
||||
return lx.errorf("trailing comma not allowed in inline tables")
|
||||
}
|
||||
return lexInlineTableValue
|
||||
case r == inlineTableEnd:
|
||||
case r == '}':
|
||||
return lexInlineTableEnd
|
||||
default:
|
||||
return lx.errorf(
|
||||
"expected a comma or an inline table terminator %q, but got %s instead",
|
||||
inlineTableEnd, runeOrEOF(r))
|
||||
return lx.errorf("expected a comma or an inline table terminator '}', but got %s instead", runeOrEOF(r))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -638,14 +675,12 @@ func lexString(lx *lexer) stateFn {
|
||||
switch {
|
||||
case r == eof:
|
||||
return lx.errorf(`unexpected EOF; expected '"'`)
|
||||
case isControl(r) || r == '\r':
|
||||
return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r)
|
||||
case isNL(r):
|
||||
return lx.errorf("strings cannot contain newlines")
|
||||
return lx.errorPrevLine(errLexStringNL{})
|
||||
case r == '\\':
|
||||
lx.push(lexString)
|
||||
return lexStringEscape
|
||||
case r == stringEnd:
|
||||
case r == '"':
|
||||
lx.backup()
|
||||
lx.emit(itemString)
|
||||
lx.next()
|
||||
@@ -660,23 +695,20 @@ func lexString(lx *lexer) stateFn {
|
||||
func lexMultilineString(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch r {
|
||||
default:
|
||||
return lexMultilineString
|
||||
case eof:
|
||||
return lx.errorf(`unexpected EOF; expected '"""'`)
|
||||
case '\r':
|
||||
if lx.peek() != '\n' {
|
||||
return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r)
|
||||
}
|
||||
return lexMultilineString
|
||||
case '\\':
|
||||
return lexMultilineStringEscape
|
||||
case stringEnd:
|
||||
case '"':
|
||||
/// Found " → try to read two more "".
|
||||
if lx.accept(stringEnd) {
|
||||
if lx.accept(stringEnd) {
|
||||
if lx.accept('"') {
|
||||
if lx.accept('"') {
|
||||
/// Peek ahead: the string can contain " and "", including at the
|
||||
/// end: """str"""""
|
||||
/// 6 or more at the end, however, is an error.
|
||||
if lx.peek() == stringEnd {
|
||||
if lx.peek() == '"' {
|
||||
/// Check if we already lexed 5 's; if so we have 6 now, and
|
||||
/// that's just too many man!
|
||||
if strings.HasSuffix(lx.current(), `"""""`) {
|
||||
@@ -699,12 +731,8 @@ func lexMultilineString(lx *lexer) stateFn {
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
return lexMultilineString
|
||||
}
|
||||
|
||||
if isControl(r) {
|
||||
return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r)
|
||||
}
|
||||
return lexMultilineString
|
||||
}
|
||||
|
||||
// lexRawString consumes a raw string. Nothing can be escaped in such a string.
|
||||
@@ -712,20 +740,19 @@ func lexMultilineString(lx *lexer) stateFn {
|
||||
func lexRawString(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
default:
|
||||
return lexRawString
|
||||
case r == eof:
|
||||
return lx.errorf(`unexpected EOF; expected "'"`)
|
||||
case isControl(r) || r == '\r':
|
||||
return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r)
|
||||
case isNL(r):
|
||||
return lx.errorf("strings cannot contain newlines")
|
||||
case r == rawStringEnd:
|
||||
return lx.errorPrevLine(errLexStringNL{})
|
||||
case r == '\'':
|
||||
lx.backup()
|
||||
lx.emit(itemRawString)
|
||||
lx.next()
|
||||
lx.ignore()
|
||||
return lx.pop()
|
||||
}
|
||||
return lexRawString
|
||||
}
|
||||
|
||||
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such
|
||||
@@ -734,21 +761,18 @@ func lexRawString(lx *lexer) stateFn {
|
||||
func lexMultilineRawString(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch r {
|
||||
default:
|
||||
return lexMultilineRawString
|
||||
case eof:
|
||||
return lx.errorf(`unexpected EOF; expected "'''"`)
|
||||
case '\r':
|
||||
if lx.peek() != '\n' {
|
||||
return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r)
|
||||
}
|
||||
return lexMultilineRawString
|
||||
case rawStringEnd:
|
||||
case '\'':
|
||||
/// Found ' → try to read two more ''.
|
||||
if lx.accept(rawStringEnd) {
|
||||
if lx.accept(rawStringEnd) {
|
||||
if lx.accept('\'') {
|
||||
if lx.accept('\'') {
|
||||
/// Peek ahead: the string can contain ' and '', including at the
|
||||
/// end: '''str'''''
|
||||
/// 6 or more at the end, however, is an error.
|
||||
if lx.peek() == rawStringEnd {
|
||||
if lx.peek() == '\'' {
|
||||
/// Check if we already lexed 5 's; if so we have 6 now, and
|
||||
/// that's just too many man!
|
||||
if strings.HasSuffix(lx.current(), "'''''") {
|
||||
@@ -771,12 +795,8 @@ func lexMultilineRawString(lx *lexer) stateFn {
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
return lexMultilineRawString
|
||||
}
|
||||
|
||||
if isControl(r) {
|
||||
return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r)
|
||||
}
|
||||
return lexMultilineRawString
|
||||
}
|
||||
|
||||
// lexMultilineStringEscape consumes an escaped character. It assumes that the
|
||||
@@ -817,8 +837,7 @@ func lexStringEscape(lx *lexer) stateFn {
|
||||
case 'U':
|
||||
return lexLongUnicodeEscape
|
||||
}
|
||||
return lx.errorf("invalid escape character %q; only the following escape characters are allowed: "+
|
||||
`\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r)
|
||||
return lx.error(errLexEscape{r})
|
||||
}
|
||||
|
||||
func lexShortUnicodeEscape(lx *lexer) stateFn {
|
||||
@@ -1108,8 +1127,6 @@ func lexComment(lx *lexer) stateFn {
|
||||
lx.backup()
|
||||
lx.emit(itemText)
|
||||
return lx.pop()
|
||||
case isControl(r):
|
||||
return lx.errorf("control characters are not allowed inside comments: '0x%02x'", r)
|
||||
default:
|
||||
return lexComment
|
||||
}
|
||||
@@ -1121,52 +1138,6 @@ func lexSkip(lx *lexer, nextState stateFn) stateFn {
|
||||
return nextState
|
||||
}
|
||||
|
||||
// isWhitespace returns true if `r` is a whitespace character according
|
||||
// to the spec.
|
||||
func isWhitespace(r rune) bool {
|
||||
return r == '\t' || r == ' '
|
||||
}
|
||||
|
||||
func isNL(r rune) bool {
|
||||
return r == '\n' || r == '\r'
|
||||
}
|
||||
|
||||
// Control characters except \n, \t
|
||||
func isControl(r rune) bool {
|
||||
switch r {
|
||||
case '\t', '\r', '\n':
|
||||
return false
|
||||
default:
|
||||
return (r >= 0x00 && r <= 0x1f) || r == 0x7f
|
||||
}
|
||||
}
|
||||
|
||||
func isDigit(r rune) bool {
|
||||
return r >= '0' && r <= '9'
|
||||
}
|
||||
|
||||
func isHexadecimal(r rune) bool {
|
||||
return (r >= '0' && r <= '9') ||
|
||||
(r >= 'a' && r <= 'f') ||
|
||||
(r >= 'A' && r <= 'F')
|
||||
}
|
||||
|
||||
func isOctal(r rune) bool {
|
||||
return r >= '0' && r <= '7'
|
||||
}
|
||||
|
||||
func isBinary(r rune) bool {
|
||||
return r == '0' || r == '1'
|
||||
}
|
||||
|
||||
func isBareKeyChar(r rune) bool {
|
||||
return (r >= 'A' && r <= 'Z') ||
|
||||
(r >= 'a' && r <= 'z') ||
|
||||
(r >= '0' && r <= '9') ||
|
||||
r == '_' ||
|
||||
r == '-'
|
||||
}
|
||||
|
||||
func (s stateFn) String() string {
|
||||
name := runtime.FuncForPC(reflect.ValueOf(s).Pointer()).Name()
|
||||
if i := strings.LastIndexByte(name, '.'); i > -1 {
|
||||
@@ -1223,3 +1194,26 @@ func (itype itemType) String() string {
|
||||
func (item item) String() string {
|
||||
return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val)
|
||||
}
|
||||
|
||||
func isWhitespace(r rune) bool { return r == '\t' || r == ' ' }
|
||||
func isNL(r rune) bool { return r == '\n' || r == '\r' }
|
||||
func isControl(r rune) bool { // Control characters except \t, \r, \n
|
||||
switch r {
|
||||
case '\t', '\r', '\n':
|
||||
return false
|
||||
default:
|
||||
return (r >= 0x00 && r <= 0x1f) || r == 0x7f
|
||||
}
|
||||
}
|
||||
func isDigit(r rune) bool { return r >= '0' && r <= '9' }
|
||||
func isBinary(r rune) bool { return r == '0' || r == '1' }
|
||||
func isOctal(r rune) bool { return r >= '0' && r <= '7' }
|
||||
func isHexadecimal(r rune) bool {
|
||||
return (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')
|
||||
}
|
||||
func isBareKeyChar(r rune) bool {
|
||||
return (r >= 'A' && r <= 'Z') ||
|
||||
(r >= 'a' && r <= 'z') ||
|
||||
(r >= '0' && r <= '9') ||
|
||||
r == '_' || r == '-'
|
||||
}
|
||||
|
||||
105
vendor/github.com/BurntSushi/toml/decode_meta.go → vendor/github.com/BurntSushi/toml/meta.go
generated
vendored
105
vendor/github.com/BurntSushi/toml/decode_meta.go → vendor/github.com/BurntSushi/toml/meta.go
generated
vendored
@@ -1,34 +1,39 @@
|
||||
package toml
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MetaData allows access to meta information about TOML data that may not be
|
||||
// inferable via reflection. In particular, whether a key has been defined and
|
||||
// the TOML type of a key.
|
||||
// MetaData allows access to meta information about TOML data that's not
|
||||
// accessible otherwise.
|
||||
//
|
||||
// It allows checking if a key is defined in the TOML data, whether any keys
|
||||
// were undecoded, and the TOML type of a key.
|
||||
type MetaData struct {
|
||||
context Key // Used only during decoding.
|
||||
|
||||
mapping map[string]interface{}
|
||||
types map[string]tomlType
|
||||
keys []Key
|
||||
decoded map[string]bool
|
||||
context Key // Used only during decoding.
|
||||
decoded map[string]struct{}
|
||||
}
|
||||
|
||||
// IsDefined reports if the key exists in the TOML data.
|
||||
//
|
||||
// The key should be specified hierarchically, for example to access the TOML
|
||||
// key "a.b.c" you would use:
|
||||
// key "a.b.c" you would use IsDefined("a", "b", "c"). Keys are case sensitive.
|
||||
//
|
||||
// IsDefined("a", "b", "c")
|
||||
//
|
||||
// IsDefined will return false if an empty key given. Keys are case sensitive.
|
||||
// Returns false for an empty key.
|
||||
func (md *MetaData) IsDefined(key ...string) bool {
|
||||
if len(key) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var hash map[string]interface{}
|
||||
var ok bool
|
||||
var hashOrVal interface{} = md.mapping
|
||||
var (
|
||||
hash map[string]interface{}
|
||||
ok bool
|
||||
hashOrVal interface{} = md.mapping
|
||||
)
|
||||
for _, k := range key {
|
||||
if hash, ok = hashOrVal.(map[string]interface{}); !ok {
|
||||
return false
|
||||
@@ -45,51 +50,12 @@ func (md *MetaData) IsDefined(key ...string) bool {
|
||||
// Type will return the empty string if given an empty key or a key that does
|
||||
// not exist. Keys are case sensitive.
|
||||
func (md *MetaData) Type(key ...string) string {
|
||||
fullkey := strings.Join(key, ".")
|
||||
if typ, ok := md.types[fullkey]; ok {
|
||||
if typ, ok := md.types[Key(key).String()]; ok {
|
||||
return typ.typeString()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Key represents any TOML key, including key groups. Use (MetaData).Keys to get
|
||||
// values of this type.
|
||||
type Key []string
|
||||
|
||||
func (k Key) String() string { return strings.Join(k, ".") }
|
||||
|
||||
func (k Key) maybeQuotedAll() string {
|
||||
var ss []string
|
||||
for i := range k {
|
||||
ss = append(ss, k.maybeQuoted(i))
|
||||
}
|
||||
return strings.Join(ss, ".")
|
||||
}
|
||||
|
||||
func (k Key) maybeQuoted(i int) string {
|
||||
if k[i] == "" {
|
||||
return `""`
|
||||
}
|
||||
quote := false
|
||||
for _, c := range k[i] {
|
||||
if !isBareKeyChar(c) {
|
||||
quote = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if quote {
|
||||
return `"` + quotedReplacer.Replace(k[i]) + `"`
|
||||
}
|
||||
return k[i]
|
||||
}
|
||||
|
||||
func (k Key) add(piece string) Key {
|
||||
newKey := make(Key, len(k)+1)
|
||||
copy(newKey, k)
|
||||
newKey[len(k)] = piece
|
||||
return newKey
|
||||
}
|
||||
|
||||
// Keys returns a slice of every key in the TOML data, including key groups.
|
||||
//
|
||||
// Each key is itself a slice, where the first element is the top of the
|
||||
@@ -115,9 +81,40 @@ func (md *MetaData) Keys() []Key {
|
||||
func (md *MetaData) Undecoded() []Key {
|
||||
undecoded := make([]Key, 0, len(md.keys))
|
||||
for _, key := range md.keys {
|
||||
if !md.decoded[key.String()] {
|
||||
if _, ok := md.decoded[key.String()]; !ok {
|
||||
undecoded = append(undecoded, key)
|
||||
}
|
||||
}
|
||||
return undecoded
|
||||
}
|
||||
|
||||
// Key represents any TOML key, including key groups. Use (MetaData).Keys to get
|
||||
// values of this type.
|
||||
type Key []string
|
||||
|
||||
func (k Key) String() string {
|
||||
ss := make([]string, len(k))
|
||||
for i := range k {
|
||||
ss[i] = k.maybeQuoted(i)
|
||||
}
|
||||
return strings.Join(ss, ".")
|
||||
}
|
||||
|
||||
func (k Key) maybeQuoted(i int) string {
|
||||
if k[i] == "" {
|
||||
return `""`
|
||||
}
|
||||
for _, c := range k[i] {
|
||||
if !isBareKeyChar(c) {
|
||||
return `"` + dblQuotedReplacer.Replace(k[i]) + `"`
|
||||
}
|
||||
}
|
||||
return k[i]
|
||||
}
|
||||
|
||||
func (k Key) add(piece string) Key {
|
||||
newKey := make(Key, len(k)+1)
|
||||
copy(newKey, k)
|
||||
newKey[len(k)] = piece
|
||||
return newKey
|
||||
}
|
||||
174
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
174
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
@@ -1,7 +1,6 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -12,35 +11,23 @@ import (
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
mapping map[string]interface{}
|
||||
types map[string]tomlType
|
||||
lx *lexer
|
||||
lx *lexer
|
||||
context Key // Full key for the current hash in scope.
|
||||
currentKey string // Base key name for everything except hashes.
|
||||
pos Position // Current position in the TOML file.
|
||||
|
||||
ordered []Key // List of keys in the order that they appear in the TOML data.
|
||||
context Key // Full key for the current hash in scope.
|
||||
currentKey string // Base key name for everything except hashes.
|
||||
approxLine int // Rough approximation of line number
|
||||
implicits map[string]bool // Record implied keys (e.g. 'key.group.names').
|
||||
}
|
||||
|
||||
// ParseError is used when a file can't be parsed: for example invalid integer
|
||||
// literals, duplicate keys, etc.
|
||||
type ParseError struct {
|
||||
Message string
|
||||
Line int
|
||||
LastKey string
|
||||
}
|
||||
|
||||
func (pe ParseError) Error() string {
|
||||
return fmt.Sprintf("Near line %d (last key parsed '%s'): %s",
|
||||
pe.Line, pe.LastKey, pe.Message)
|
||||
ordered []Key // List of keys in the order that they appear in the TOML data.
|
||||
mapping map[string]interface{} // Map keyname → key value.
|
||||
types map[string]tomlType // Map keyname → TOML type.
|
||||
implicits map[string]struct{} // Record implicit keys (e.g. "key.group.names").
|
||||
}
|
||||
|
||||
func parse(data string) (p *parser, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
if err, ok = r.(ParseError); ok {
|
||||
if pErr, ok := r.(ParseError); ok {
|
||||
pErr.input = data
|
||||
err = pErr
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
@@ -60,8 +47,13 @@ func parse(data string) (p *parser, err error) {
|
||||
if len(data) < 6 {
|
||||
ex = len(data)
|
||||
}
|
||||
if strings.ContainsRune(data[:ex], 0) {
|
||||
return nil, errors.New("files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8")
|
||||
if i := strings.IndexRune(data[:ex], 0); i > -1 {
|
||||
return nil, ParseError{
|
||||
Message: "files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8",
|
||||
Position: Position{Line: 1, Start: i, Len: 1},
|
||||
Line: 1,
|
||||
input: data,
|
||||
}
|
||||
}
|
||||
|
||||
p = &parser{
|
||||
@@ -69,7 +61,7 @@ func parse(data string) (p *parser, err error) {
|
||||
types: make(map[string]tomlType),
|
||||
lx: lex(data),
|
||||
ordered: make([]Key, 0),
|
||||
implicits: make(map[string]bool),
|
||||
implicits: make(map[string]struct{}),
|
||||
}
|
||||
for {
|
||||
item := p.next()
|
||||
@@ -82,12 +74,21 @@ func parse(data string) (p *parser, err error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *parser) panicf(format string, v ...interface{}) {
|
||||
msg := fmt.Sprintf(format, v...)
|
||||
func (p *parser) panicItemf(it item, format string, v ...interface{}) {
|
||||
panic(ParseError{
|
||||
Message: msg,
|
||||
Line: p.approxLine,
|
||||
LastKey: p.current(),
|
||||
Message: fmt.Sprintf(format, v...),
|
||||
Position: it.pos,
|
||||
Line: it.pos.Len,
|
||||
LastKey: p.current(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *parser) panicf(format string, v ...interface{}) {
|
||||
panic(ParseError{
|
||||
Message: fmt.Sprintf(format, v...),
|
||||
Position: p.pos,
|
||||
Line: p.pos.Line,
|
||||
LastKey: p.current(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -95,11 +96,26 @@ func (p *parser) next() item {
|
||||
it := p.lx.nextItem()
|
||||
//fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.line, it.val)
|
||||
if it.typ == itemError {
|
||||
p.panicf("%s", it.val)
|
||||
if it.err != nil {
|
||||
panic(ParseError{
|
||||
Position: it.pos,
|
||||
Line: it.pos.Line,
|
||||
LastKey: p.current(),
|
||||
err: it.err,
|
||||
})
|
||||
}
|
||||
|
||||
p.panicItemf(it, "%s", it.val)
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) nextPos() item {
|
||||
it := p.next()
|
||||
p.pos = it.pos
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) bug(format string, v ...interface{}) {
|
||||
panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
|
||||
}
|
||||
@@ -119,11 +135,9 @@ func (p *parser) assertEqual(expected, got itemType) {
|
||||
func (p *parser) topLevel(item item) {
|
||||
switch item.typ {
|
||||
case itemCommentStart: // # ..
|
||||
p.approxLine = item.line
|
||||
p.expect(itemText)
|
||||
case itemTableStart: // [ .. ]
|
||||
name := p.next()
|
||||
p.approxLine = name.line
|
||||
name := p.nextPos()
|
||||
|
||||
var key Key
|
||||
for ; name.typ != itemTableEnd && name.typ != itemEOF; name = p.next() {
|
||||
@@ -135,8 +149,7 @@ func (p *parser) topLevel(item item) {
|
||||
p.setType("", tomlHash)
|
||||
p.ordered = append(p.ordered, key)
|
||||
case itemArrayTableStart: // [[ .. ]]
|
||||
name := p.next()
|
||||
p.approxLine = name.line
|
||||
name := p.nextPos()
|
||||
|
||||
var key Key
|
||||
for ; name.typ != itemArrayTableEnd && name.typ != itemEOF; name = p.next() {
|
||||
@@ -150,8 +163,7 @@ func (p *parser) topLevel(item item) {
|
||||
case itemKeyStart: // key = ..
|
||||
outerContext := p.context
|
||||
/// Read all the key parts (e.g. 'a' and 'b' in 'a.b')
|
||||
k := p.next()
|
||||
p.approxLine = k.line
|
||||
k := p.nextPos()
|
||||
var key Key
|
||||
for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
|
||||
key = append(key, p.keyString(k))
|
||||
@@ -206,9 +218,9 @@ var datetimeRepl = strings.NewReplacer(
|
||||
func (p *parser) value(it item, parentIsArray bool) (interface{}, tomlType) {
|
||||
switch it.typ {
|
||||
case itemString:
|
||||
return p.replaceEscapes(it.val), p.typeOfPrimitive(it)
|
||||
return p.replaceEscapes(it, it.val), p.typeOfPrimitive(it)
|
||||
case itemMultilineString:
|
||||
return p.replaceEscapes(stripFirstNewline(stripEscapedNewlines(it.val))), p.typeOfPrimitive(it)
|
||||
return p.replaceEscapes(it, stripFirstNewline(stripEscapedNewlines(it.val))), p.typeOfPrimitive(it)
|
||||
case itemRawString:
|
||||
return it.val, p.typeOfPrimitive(it)
|
||||
case itemRawMultilineString:
|
||||
@@ -240,10 +252,10 @@ func (p *parser) value(it item, parentIsArray bool) (interface{}, tomlType) {
|
||||
|
||||
func (p *parser) valueInteger(it item) (interface{}, tomlType) {
|
||||
if !numUnderscoresOK(it.val) {
|
||||
p.panicf("Invalid integer %q: underscores must be surrounded by digits", it.val)
|
||||
p.panicItemf(it, "Invalid integer %q: underscores must be surrounded by digits", it.val)
|
||||
}
|
||||
if numHasLeadingZero(it.val) {
|
||||
p.panicf("Invalid integer %q: cannot have leading zeroes", it.val)
|
||||
p.panicItemf(it, "Invalid integer %q: cannot have leading zeroes", it.val)
|
||||
}
|
||||
|
||||
num, err := strconv.ParseInt(it.val, 0, 64)
|
||||
@@ -254,7 +266,7 @@ func (p *parser) valueInteger(it item) (interface{}, tomlType) {
|
||||
// So mark the former as a bug but the latter as a legitimate user
|
||||
// error.
|
||||
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
|
||||
p.panicf("Integer '%s' is out of the range of 64-bit signed integers.", it.val)
|
||||
p.panicItemf(it, "Integer '%s' is out of the range of 64-bit signed integers.", it.val)
|
||||
} else {
|
||||
p.bug("Expected integer value, but got '%s'.", it.val)
|
||||
}
|
||||
@@ -272,18 +284,18 @@ func (p *parser) valueFloat(it item) (interface{}, tomlType) {
|
||||
})
|
||||
for _, part := range parts {
|
||||
if !numUnderscoresOK(part) {
|
||||
p.panicf("Invalid float %q: underscores must be surrounded by digits", it.val)
|
||||
p.panicItemf(it, "Invalid float %q: underscores must be surrounded by digits", it.val)
|
||||
}
|
||||
}
|
||||
if len(parts) > 0 && numHasLeadingZero(parts[0]) {
|
||||
p.panicf("Invalid float %q: cannot have leading zeroes", it.val)
|
||||
p.panicItemf(it, "Invalid float %q: cannot have leading zeroes", it.val)
|
||||
}
|
||||
if !numPeriodsOK(it.val) {
|
||||
// As a special case, numbers like '123.' or '1.e2',
|
||||
// which are valid as far as Go/strconv are concerned,
|
||||
// must be rejected because TOML says that a fractional
|
||||
// part consists of '.' followed by 1+ digits.
|
||||
p.panicf("Invalid float %q: '.' must be followed by one or more digits", it.val)
|
||||
p.panicItemf(it, "Invalid float %q: '.' must be followed by one or more digits", it.val)
|
||||
}
|
||||
val := strings.Replace(it.val, "_", "", -1)
|
||||
if val == "+nan" || val == "-nan" { // Go doesn't support this, but TOML spec does.
|
||||
@@ -292,9 +304,9 @@ func (p *parser) valueFloat(it item) (interface{}, tomlType) {
|
||||
num, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
|
||||
p.panicf("Float '%s' is out of the range of 64-bit IEEE-754 floating-point numbers.", it.val)
|
||||
p.panicItemf(it, "Float '%s' is out of the range of 64-bit IEEE-754 floating-point numbers.", it.val)
|
||||
} else {
|
||||
p.panicf("Invalid float value: %q", it.val)
|
||||
p.panicItemf(it, "Invalid float value: %q", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
@@ -325,7 +337,7 @@ func (p *parser) valueDatetime(it item) (interface{}, tomlType) {
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
p.panicf("Invalid TOML Datetime: %q.", it.val)
|
||||
p.panicItemf(it, "Invalid TOML Datetime: %q.", it.val)
|
||||
}
|
||||
return t, p.typeOfPrimitive(it)
|
||||
}
|
||||
@@ -335,8 +347,12 @@ func (p *parser) valueArray(it item) (interface{}, tomlType) {
|
||||
|
||||
// p.setType(p.currentKey, typ)
|
||||
var (
|
||||
array []interface{}
|
||||
types []tomlType
|
||||
|
||||
// Initialize to a non-nil empty slice. This makes it consistent with
|
||||
// how S = [] decodes into a non-nil slice inside something like struct
|
||||
// { S []string }. See #338
|
||||
array = []interface{}{}
|
||||
)
|
||||
for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
|
||||
if it.typ == itemCommentStart {
|
||||
@@ -347,6 +363,12 @@ func (p *parser) valueArray(it item) (interface{}, tomlType) {
|
||||
val, typ := p.value(it, true)
|
||||
array = append(array, val)
|
||||
types = append(types, typ)
|
||||
|
||||
// XXX: types isn't used here, we need it to record the accurate type
|
||||
// information.
|
||||
//
|
||||
// Not entirely sure how to best store this; could use "key[0]",
|
||||
// "key[1]" notation, or maybe store it on the Array type?
|
||||
}
|
||||
return array, tomlArray
|
||||
}
|
||||
@@ -373,8 +395,7 @@ func (p *parser) valueInlineTable(it item, parentIsArray bool) (interface{}, tom
|
||||
}
|
||||
|
||||
/// Read all key parts.
|
||||
k := p.next()
|
||||
p.approxLine = k.line
|
||||
k := p.nextPos()
|
||||
var key Key
|
||||
for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
|
||||
key = append(key, p.keyString(k))
|
||||
@@ -408,7 +429,7 @@ func (p *parser) valueInlineTable(it item, parentIsArray bool) (interface{}, tom
|
||||
// numHasLeadingZero checks if this number has leading zeroes, allowing for '0',
|
||||
// +/- signs, and base prefixes.
|
||||
func numHasLeadingZero(s string) bool {
|
||||
if len(s) > 1 && s[0] == '0' && isDigit(rune(s[1])) { // >1 to allow "0" and isDigit to allow 0x
|
||||
if len(s) > 1 && s[0] == '0' && !(s[1] == 'b' || s[1] == 'o' || s[1] == 'x') { // Allow 0b, 0o, 0x
|
||||
return true
|
||||
}
|
||||
if len(s) > 2 && (s[0] == '-' || s[0] == '+') && s[1] == '0' {
|
||||
@@ -503,7 +524,7 @@ func (p *parser) addContext(key Key, array bool) {
|
||||
if hash, ok := hashContext[k].([]map[string]interface{}); ok {
|
||||
hashContext[k] = append(hash, make(map[string]interface{}))
|
||||
} else {
|
||||
p.panicf("Key '%s' was already created and cannot be used as an array.", keyContext)
|
||||
p.panicf("Key '%s' was already created and cannot be used as an array.", key)
|
||||
}
|
||||
} else {
|
||||
p.setValue(key[len(key)-1], make(map[string]interface{}))
|
||||
@@ -513,8 +534,8 @@ func (p *parser) addContext(key Key, array bool) {
|
||||
|
||||
// set calls setValue and setType.
|
||||
func (p *parser) set(key string, val interface{}, typ tomlType) {
|
||||
p.setValue(p.currentKey, val)
|
||||
p.setType(p.currentKey, typ)
|
||||
p.setValue(key, val)
|
||||
p.setType(key, typ)
|
||||
}
|
||||
|
||||
// setValue sets the given key to the given value in the current context.
|
||||
@@ -573,27 +594,31 @@ func (p *parser) setValue(key string, value interface{}) {
|
||||
hash[key] = value
|
||||
}
|
||||
|
||||
// setType sets the type of a particular value at a given key.
|
||||
// It should be called immediately AFTER setValue.
|
||||
// setType sets the type of a particular value at a given key. It should be
|
||||
// called immediately AFTER setValue.
|
||||
//
|
||||
// Note that if `key` is empty, then the type given will be applied to the
|
||||
// current context (which is either a table or an array of tables).
|
||||
func (p *parser) setType(key string, typ tomlType) {
|
||||
keyContext := make(Key, 0, len(p.context)+1)
|
||||
for _, k := range p.context {
|
||||
keyContext = append(keyContext, k)
|
||||
}
|
||||
keyContext = append(keyContext, p.context...)
|
||||
if len(key) > 0 { // allow type setting for hashes
|
||||
keyContext = append(keyContext, key)
|
||||
}
|
||||
// Special case to make empty keys ("" = 1) work.
|
||||
// Without it it will set "" rather than `""`.
|
||||
// TODO: why is this needed? And why is this only needed here?
|
||||
if len(keyContext) == 0 {
|
||||
keyContext = Key{""}
|
||||
}
|
||||
p.types[keyContext.String()] = typ
|
||||
}
|
||||
|
||||
// Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and
|
||||
// "[a.b.c]" (the "a", "b", and "c" hashes are never created explicitly).
|
||||
func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = true }
|
||||
func (p *parser) removeImplicit(key Key) { p.implicits[key.String()] = false }
|
||||
func (p *parser) isImplicit(key Key) bool { return p.implicits[key.String()] }
|
||||
func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struct{}{} }
|
||||
func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) }
|
||||
func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok }
|
||||
func (p *parser) isArray(key Key) bool { return p.types[key.String()] == tomlArray }
|
||||
func (p *parser) addImplicitContext(key Key) {
|
||||
p.addImplicit(key)
|
||||
@@ -662,8 +687,8 @@ func stripEscapedNewlines(s string) string {
|
||||
return strings.Join(split, "")
|
||||
}
|
||||
|
||||
func (p *parser) replaceEscapes(str string) string {
|
||||
var replaced []rune
|
||||
func (p *parser) replaceEscapes(it item, str string) string {
|
||||
replaced := make([]rune, 0, len(str))
|
||||
s := []byte(str)
|
||||
r := 0
|
||||
for r < len(s) {
|
||||
@@ -683,7 +708,7 @@ func (p *parser) replaceEscapes(str string) string {
|
||||
p.bug("Expected valid escape code after \\, but got %q.", s[r])
|
||||
return ""
|
||||
case ' ', '\t':
|
||||
p.panicf("invalid escape: '\\%c'", s[r])
|
||||
p.panicItemf(it, "invalid escape: '\\%c'", s[r])
|
||||
return ""
|
||||
case 'b':
|
||||
replaced = append(replaced, rune(0x0008))
|
||||
@@ -710,14 +735,14 @@ func (p *parser) replaceEscapes(str string) string {
|
||||
// At this point, we know we have a Unicode escape of the form
|
||||
// `uXXXX` at [r, r+5). (Because the lexer guarantees this
|
||||
// for us.)
|
||||
escaped := p.asciiEscapeToUnicode(s[r+1 : r+5])
|
||||
escaped := p.asciiEscapeToUnicode(it, s[r+1:r+5])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 5
|
||||
case 'U':
|
||||
// At this point, we know we have a Unicode escape of the form
|
||||
// `uXXXX` at [r, r+9). (Because the lexer guarantees this
|
||||
// for us.)
|
||||
escaped := p.asciiEscapeToUnicode(s[r+1 : r+9])
|
||||
escaped := p.asciiEscapeToUnicode(it, s[r+1:r+9])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 9
|
||||
}
|
||||
@@ -725,15 +750,14 @@ func (p *parser) replaceEscapes(str string) string {
|
||||
return string(replaced)
|
||||
}
|
||||
|
||||
func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
|
||||
func (p *parser) asciiEscapeToUnicode(it item, bs []byte) rune {
|
||||
s := string(bs)
|
||||
hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32)
|
||||
if err != nil {
|
||||
p.bug("Could not parse '%s' as a hexadecimal number, but the "+
|
||||
"lexer claims it's OK: %s", s, err)
|
||||
p.bug("Could not parse '%s' as a hexadecimal number, but the lexer claims it's OK: %s", s, err)
|
||||
}
|
||||
if !utf8.ValidRune(rune(hex)) {
|
||||
p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
|
||||
p.panicItemf(it, "Escaped character '\\u%s' is not valid UTF-8.", s)
|
||||
}
|
||||
return rune(hex)
|
||||
}
|
||||
|
||||
4
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
4
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
@@ -70,8 +70,8 @@ func typeFields(t reflect.Type) []field {
|
||||
next := []field{{typ: t}}
|
||||
|
||||
// Count of queued names for current level and the next.
|
||||
count := map[reflect.Type]int{}
|
||||
nextCount := map[reflect.Type]int{}
|
||||
var count map[reflect.Type]int
|
||||
var nextCount map[reflect.Type]int
|
||||
|
||||
// Types already visited at an earlier level.
|
||||
visited := map[reflect.Type]bool{}
|
||||
|
||||
@@ -16,7 +16,7 @@ func typeEqual(t1, t2 tomlType) bool {
|
||||
return t1.typeString() == t2.typeString()
|
||||
}
|
||||
|
||||
func typeIsHash(t tomlType) bool {
|
||||
func typeIsTable(t tomlType) bool {
|
||||
return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash)
|
||||
}
|
||||
|
||||
27
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
27
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
@@ -11,12 +11,27 @@ package.
|
||||
|
||||
Please see the LICENSE file for licensing information.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of
|
||||
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information
|
||||
see the [Code of Conduct
|
||||
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
|
||||
[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
|
||||
questions or comments.
|
||||
## Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA)
|
||||
declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR
|
||||
appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
We also require that contributors sign their commits using git commit -s or git commit --signoff to certify they either authored the work themselves
|
||||
or otherwise have permission to use it in this project. Please see https://developercertificate.org/ for more info, as well as to make sure that you can
|
||||
attest to the rules listed. Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off.
|
||||
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
|
||||
|
||||
## Special Thanks
|
||||
Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe
|
||||
for another named pipe implementation.
|
||||
|
||||
74
vendor/github.com/Microsoft/go-winio/backuptar/tar.go
generated
vendored
74
vendor/github.com/Microsoft/go-winio/backuptar/tar.go
generated
vendored
@@ -5,7 +5,6 @@ package backuptar
|
||||
import (
|
||||
"archive/tar"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -42,19 +41,14 @@ const (
|
||||
hdrCreationTime = "LIBARCHIVE.creationtime"
|
||||
)
|
||||
|
||||
func writeZeroes(w io.Writer, count int64) error {
|
||||
buf := make([]byte, 8192)
|
||||
c := len(buf)
|
||||
for i := int64(0); i < count; i += int64(c) {
|
||||
if int64(c) > count-i {
|
||||
c = int(count - i)
|
||||
}
|
||||
_, err := w.Write(buf[:c])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// zeroReader is an io.Reader that always returns 0s.
|
||||
type zeroReader struct{}
|
||||
|
||||
func (zr zeroReader) Read(b []byte) (int, error) {
|
||||
for i := range b {
|
||||
b[i] = 0
|
||||
}
|
||||
return nil
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
|
||||
@@ -71,16 +65,26 @@ func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
|
||||
return fmt.Errorf("unexpected stream %d", bhdr.Id)
|
||||
}
|
||||
|
||||
// We can't seek backwards, since we have already written that data to the tar.Writer.
|
||||
if bhdr.Offset < curOffset {
|
||||
return fmt.Errorf("cannot seek back from %d to %d", curOffset, bhdr.Offset)
|
||||
}
|
||||
// archive/tar does not support writing sparse files
|
||||
// so just write zeroes to catch up to the current offset.
|
||||
err = writeZeroes(t, bhdr.Offset-curOffset)
|
||||
if _, err := io.CopyN(t, zeroReader{}, bhdr.Offset-curOffset); err != nil {
|
||||
return fmt.Errorf("seek to offset %d: %s", bhdr.Offset, err)
|
||||
}
|
||||
if bhdr.Size == 0 {
|
||||
// A sparse block with size = 0 is used to mark the end of the sparse blocks.
|
||||
break
|
||||
}
|
||||
n, err := io.Copy(t, br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != bhdr.Size {
|
||||
return fmt.Errorf("copied %d bytes instead of %d at offset %d", n, bhdr.Size, bhdr.Offset)
|
||||
}
|
||||
curOffset = bhdr.Offset + n
|
||||
}
|
||||
return nil
|
||||
@@ -221,20 +225,44 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size
|
||||
}
|
||||
}
|
||||
|
||||
// The logic for copying file contents is fairly complicated due to the need for handling sparse files,
|
||||
// and the weird ways they are represented by BackupRead. A normal file will always either have a data stream
|
||||
// with size and content, or no data stream at all (if empty). However, for a sparse file, the content can also
|
||||
// be represented using a series of sparse block streams following the data stream. Additionally, the way sparse
|
||||
// files are handled by BackupRead has changed in the OS recently. The specifics of the representation are described
|
||||
// in the list at the bottom of this block comment.
|
||||
//
|
||||
// Sparse files can be represented in four different ways, based on the specifics of the file.
|
||||
// - Size = 0:
|
||||
// Previously: BackupRead yields no data stream and no sparse block streams.
|
||||
// Recently: BackupRead yields a data stream with size = 0. There are no following sparse block streams.
|
||||
// - Size > 0, no allocated ranges:
|
||||
// BackupRead yields a data stream with size = 0. Following is a single sparse block stream with
|
||||
// size = 0 and offset = <file size>.
|
||||
// - Size > 0, one allocated range:
|
||||
// BackupRead yields a data stream with size = <file size> containing the file contents. There are no
|
||||
// sparse block streams. This is the case if you take a normal file with contents and simply set the
|
||||
// sparse flag on it.
|
||||
// - Size > 0, multiple allocated ranges:
|
||||
// BackupRead yields a data stream with size = 0. Following are sparse block streams for each allocated
|
||||
// range of the file containing the range contents. Finally there is a sparse block stream with
|
||||
// size = 0 and offset = <file size>.
|
||||
|
||||
if dataHdr != nil {
|
||||
// A data stream was found. Copy the data.
|
||||
if (dataHdr.Attributes & winio.StreamSparseAttributes) == 0 {
|
||||
// We assume that we will either have a data stream size > 0 XOR have sparse block streams.
|
||||
if dataHdr.Size > 0 || (dataHdr.Attributes&winio.StreamSparseAttributes) == 0 {
|
||||
if size != dataHdr.Size {
|
||||
return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size)
|
||||
}
|
||||
_, err = io.Copy(t, br)
|
||||
if err != nil {
|
||||
return err
|
||||
if _, err = io.Copy(t, br); err != nil {
|
||||
return fmt.Errorf("%s: copying contents from data stream: %s", name, err)
|
||||
}
|
||||
} else {
|
||||
err = copySparse(t, br)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if size > 0 {
|
||||
// As of a recent OS change, BackupRead now returns a data stream for empty sparse files.
|
||||
// These files have no sparse block streams, so skip the copySparse call if file size = 0.
|
||||
if err = copySparse(t, br); err != nil {
|
||||
return fmt.Errorf("%s: copying contents from sparse block stream: %s", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,7 +307,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size
|
||||
} else {
|
||||
// Unsupported for now, since the size of the alternate stream is not present
|
||||
// in the backup stream until after the data has been read.
|
||||
return errors.New("tar of sparse alternate data streams is unsupported")
|
||||
return fmt.Errorf("%s: tar of sparse alternate data streams is unsupported", name)
|
||||
}
|
||||
case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
|
||||
// ignore these streams
|
||||
|
||||
6
vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go
generated
vendored
6
vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go
generated
vendored
@@ -2,6 +2,6 @@ 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
|
||||
//sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) = advapi32.GetSecurityInfo
|
||||
//sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) = advapi32.SetSecurityInfo
|
||||
//sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) = advapi32.SetEntriesInAclW
|
||||
|
||||
24
vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go
generated
vendored
24
vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go
generated
vendored
@@ -45,26 +45,26 @@ var (
|
||||
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)
|
||||
func getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) {
|
||||
r0, _, _ := 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 r0 != 0 {
|
||||
win32err = syscall.Errno(r0)
|
||||
}
|
||||
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)
|
||||
func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) {
|
||||
r0, _, _ := syscall.Syscall6(procSetEntriesInAclW.Addr(), 4, uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl)), 0, 0)
|
||||
if r0 != 0 {
|
||||
win32err = syscall.Errno(r0)
|
||||
}
|
||||
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)
|
||||
func setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) {
|
||||
r0, _, _ := syscall.Syscall9(procSetSecurityInfo.Addr(), 7, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl), 0, 0)
|
||||
if r0 != 0 {
|
||||
win32err = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
10
vendor/github.com/Microsoft/go-winio/vhd/vhd.go
generated
vendored
10
vendor/github.com/Microsoft/go-winio/vhd/vhd.go
generated
vendored
@@ -13,11 +13,11 @@ import (
|
||||
|
||||
//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
|
||||
//sys createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) = virtdisk.CreateVirtualDisk
|
||||
//sys openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (win32err error) = virtdisk.OpenVirtualDisk
|
||||
//sys attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) = virtdisk.AttachVirtualDisk
|
||||
//sys detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) = virtdisk.DetachVirtualDisk
|
||||
//sys getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) = virtdisk.GetVirtualDiskPhysicalPath
|
||||
|
||||
type (
|
||||
CreateVirtualDiskFlag uint32
|
||||
|
||||
52
vendor/github.com/Microsoft/go-winio/vhd/zvhd_windows.go
generated
vendored
52
vendor/github.com/Microsoft/go-winio/vhd/zvhd_windows.go
generated
vendored
@@ -47,60 +47,60 @@ var (
|
||||
procOpenVirtualDisk = modvirtdisk.NewProc("OpenVirtualDisk")
|
||||
)
|
||||
|
||||
func attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procAttachVirtualDisk.Addr(), 6, uintptr(handle), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(attachVirtualDiskFlag), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)))
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
func attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) {
|
||||
r0, _, _ := syscall.Syscall6(procAttachVirtualDisk.Addr(), 6, uintptr(handle), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(attachVirtualDiskFlag), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)))
|
||||
if r0 != 0 {
|
||||
win32err = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) {
|
||||
func createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) {
|
||||
var _p0 *uint16
|
||||
_p0, err = syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
_p0, win32err = syscall.UTF16PtrFromString(path)
|
||||
if win32err != nil {
|
||||
return
|
||||
}
|
||||
return _createVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, securityDescriptor, createVirtualDiskFlags, providerSpecificFlags, parameters, overlapped, handle)
|
||||
}
|
||||
|
||||
func _createVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) {
|
||||
r1, _, e1 := syscall.Syscall9(procCreateVirtualDisk.Addr(), 9, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(createVirtualDiskFlags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(handle)))
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
func _createVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) {
|
||||
r0, _, _ := syscall.Syscall9(procCreateVirtualDisk.Addr(), 9, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(createVirtualDiskFlags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(handle)))
|
||||
if r0 != 0 {
|
||||
win32err = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procDetachVirtualDisk.Addr(), 3, uintptr(handle), uintptr(detachVirtualDiskFlags), uintptr(providerSpecificFlags))
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
func detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) {
|
||||
r0, _, _ := syscall.Syscall(procDetachVirtualDisk.Addr(), 3, uintptr(handle), uintptr(detachVirtualDiskFlags), uintptr(providerSpecificFlags))
|
||||
if r0 != 0 {
|
||||
win32err = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procGetVirtualDiskPhysicalPath.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(diskPathSizeInBytes)), uintptr(unsafe.Pointer(buffer)))
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
func getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) {
|
||||
r0, _, _ := syscall.Syscall(procGetVirtualDiskPhysicalPath.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(diskPathSizeInBytes)), uintptr(unsafe.Pointer(buffer)))
|
||||
if r0 != 0 {
|
||||
win32err = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) {
|
||||
func openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (win32err error) {
|
||||
var _p0 *uint16
|
||||
_p0, err = syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
_p0, win32err = syscall.UTF16PtrFromString(path)
|
||||
if win32err != nil {
|
||||
return
|
||||
}
|
||||
return _openVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, openVirtualDiskFlags, parameters, handle)
|
||||
}
|
||||
|
||||
func _openVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procOpenVirtualDisk.Addr(), 6, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(openVirtualDiskFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle)))
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
func _openVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (win32err error) {
|
||||
r0, _, _ := syscall.Syscall6(procOpenVirtualDisk.Addr(), 6, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(openVirtualDiskFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle)))
|
||||
if r0 != 0 {
|
||||
win32err = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
39
vendor/github.com/Microsoft/hcsshim/.gitignore
generated
vendored
39
vendor/github.com/Microsoft/hcsshim/.gitignore
generated
vendored
@@ -1,3 +1,38 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
.idea
|
||||
.vscode
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Ignore vscode setting files
|
||||
.vscode/
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
# Ignore gcs bin directory
|
||||
service/bin/
|
||||
service/pkg/
|
||||
|
||||
*.img
|
||||
*.vhd
|
||||
*.tar.gz
|
||||
|
||||
# Make stuff
|
||||
.rootfs-done
|
||||
bin/*
|
||||
rootfs/*
|
||||
*.o
|
||||
/build/
|
||||
|
||||
deps/*
|
||||
out/*
|
||||
|
||||
.idea/
|
||||
.vscode/
|
||||
99
vendor/github.com/Microsoft/hcsshim/.golangci.yml
generated
vendored
Normal file
99
vendor/github.com/Microsoft/hcsshim/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
run:
|
||||
timeout: 8m
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- stylecheck
|
||||
|
||||
linters-settings:
|
||||
stylecheck:
|
||||
# https://staticcheck.io/docs/checks
|
||||
checks: ["all"]
|
||||
|
||||
|
||||
issues:
|
||||
# This repo has a LOT of generated schema files, operating system bindings, and other things that ST1003 from stylecheck won't like
|
||||
# (screaming case Windows api constants for example). There's also some structs that we *could* change the initialisms to be Go
|
||||
# friendly (Id -> ID) but they're exported and it would be a breaking change. This makes it so that most new code, code that isn't
|
||||
# supposed to be a pretty faithful mapping to an OS call/constants, or non-generated code still checks if we're following idioms,
|
||||
# while ignoring the things that are just noise or would be more of a hassle than it'd be worth to change.
|
||||
exclude-rules:
|
||||
- path: layer.go
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: hcsshim.go
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: internal\\hcs\\schema2\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: internal\\wclayer\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: hcn\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: internal\\hcs\\schema1\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: internal\\hns\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: ext4\\internal\\compactext4\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: ext4\\internal\\format\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: internal\\guestrequest\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: internal\\guest\\prot\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: internal\\windevice\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: internal\\winapi\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: internal\\vmcompute\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: internal\\regstate\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
|
||||
- path: internal\\hcserror\\
|
||||
linters:
|
||||
- stylecheck
|
||||
Text: "ST1003:"
|
||||
87
vendor/github.com/Microsoft/hcsshim/Makefile
generated
vendored
Normal file
87
vendor/github.com/Microsoft/hcsshim/Makefile
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
BASE:=base.tar.gz
|
||||
|
||||
GO:=go
|
||||
GO_FLAGS:=-ldflags "-s -w" # strip Go binaries
|
||||
CGO_ENABLED:=0
|
||||
GOMODVENDOR:=
|
||||
|
||||
CFLAGS:=-O2 -Wall
|
||||
LDFLAGS:=-static -s # strip C binaries
|
||||
|
||||
GO_FLAGS_EXTRA:=
|
||||
ifeq "$(GOMODVENDOR)" "1"
|
||||
GO_FLAGS_EXTRA += -mod=vendor
|
||||
endif
|
||||
GO_BUILD:=CGO_ENABLED=$(CGO_ENABLED) $(GO) build $(GO_FLAGS) $(GO_FLAGS_EXTRA)
|
||||
|
||||
SRCROOT=$(dir $(abspath $(firstword $(MAKEFILE_LIST))))
|
||||
|
||||
# The link aliases for gcstools
|
||||
GCS_TOOLS=\
|
||||
generichook
|
||||
|
||||
.PHONY: all always rootfs test
|
||||
|
||||
all: out/initrd.img out/rootfs.tar.gz
|
||||
|
||||
clean:
|
||||
find -name '*.o' -print0 | xargs -0 -r rm
|
||||
rm -rf bin deps rootfs out
|
||||
|
||||
test:
|
||||
cd $(SRCROOT) && go test -v ./internal/guest/...
|
||||
|
||||
out/delta.tar.gz: bin/init bin/vsockexec bin/cmd/gcs bin/cmd/gcstools Makefile
|
||||
@mkdir -p out
|
||||
rm -rf rootfs
|
||||
mkdir -p rootfs/bin/
|
||||
cp bin/init rootfs/
|
||||
cp bin/vsockexec rootfs/bin/
|
||||
cp bin/cmd/gcs rootfs/bin/
|
||||
cp bin/cmd/gcstools rootfs/bin/
|
||||
for tool in $(GCS_TOOLS); do ln -s gcstools rootfs/bin/$$tool; done
|
||||
git -C $(SRCROOT) rev-parse HEAD > rootfs/gcs.commit && \
|
||||
git -C $(SRCROOT) rev-parse --abbrev-ref HEAD > rootfs/gcs.branch
|
||||
tar -zcf $@ -C rootfs .
|
||||
rm -rf rootfs
|
||||
|
||||
out/rootfs.tar.gz: out/initrd.img
|
||||
rm -rf rootfs-conv
|
||||
mkdir rootfs-conv
|
||||
gunzip -c out/initrd.img | (cd rootfs-conv && cpio -imd)
|
||||
tar -zcf $@ -C rootfs-conv .
|
||||
rm -rf rootfs-conv
|
||||
|
||||
out/initrd.img: $(BASE) out/delta.tar.gz $(SRCROOT)/hack/catcpio.sh
|
||||
$(SRCROOT)/hack/catcpio.sh "$(BASE)" out/delta.tar.gz > out/initrd.img.uncompressed
|
||||
gzip -c out/initrd.img.uncompressed > $@
|
||||
rm out/initrd.img.uncompressed
|
||||
|
||||
-include deps/cmd/gcs.gomake
|
||||
-include deps/cmd/gcstools.gomake
|
||||
|
||||
# Implicit rule for includes that define Go targets.
|
||||
%.gomake: $(SRCROOT)/Makefile
|
||||
@mkdir -p $(dir $@)
|
||||
@/bin/echo $(@:deps/%.gomake=bin/%): $(SRCROOT)/hack/gomakedeps.sh > $@.new
|
||||
@/bin/echo -e '\t@mkdir -p $$(dir $$@) $(dir $@)' >> $@.new
|
||||
@/bin/echo -e '\t$$(GO_BUILD) -o $$@.new $$(SRCROOT)/$$(@:bin/%=%)' >> $@.new
|
||||
@/bin/echo -e '\tGO="$(GO)" $$(SRCROOT)/hack/gomakedeps.sh $$@ $$(SRCROOT)/$$(@:bin/%=%) $$(GO_FLAGS) $$(GO_FLAGS_EXTRA) > $(@:%.gomake=%.godeps).new' >> $@.new
|
||||
@/bin/echo -e '\tmv $(@:%.gomake=%.godeps).new $(@:%.gomake=%.godeps)' >> $@.new
|
||||
@/bin/echo -e '\tmv $$@.new $$@' >> $@.new
|
||||
@/bin/echo -e '-include $(@:%.gomake=%.godeps)' >> $@.new
|
||||
mv $@.new $@
|
||||
|
||||
VPATH=$(SRCROOT)
|
||||
|
||||
bin/vsockexec: vsockexec/vsockexec.o vsockexec/vsock.o
|
||||
@mkdir -p bin
|
||||
$(CC) $(LDFLAGS) -o $@ $^
|
||||
|
||||
bin/init: init/init.o vsockexec/vsock.o
|
||||
@mkdir -p bin
|
||||
$(CC) $(LDFLAGS) -o $@ $^
|
||||
|
||||
%.o: %.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
|
||||
82
vendor/github.com/Microsoft/hcsshim/README.md
generated
vendored
82
vendor/github.com/Microsoft/hcsshim/README.md
generated
vendored
@@ -2,13 +2,67 @@
|
||||
|
||||
[](https://github.com/microsoft/hcsshim/actions?query=branch%3Amaster)
|
||||
|
||||
This package contains the Golang interface for using the Windows [Host Compute Service](https://techcommunity.microsoft.com/t5/containers/introducing-the-host-compute-service-hcs/ba-p/382332) (HCS) to launch and manage [Windows Containers](https://docs.microsoft.com/en-us/virtualization/windowscontainers/about/). It also contains other helpers and functions for managing Windows Containers such as the Golang interface for the Host Network Service (HNS).
|
||||
This package contains the Golang interface for using the Windows [Host Compute Service](https://techcommunity.microsoft.com/t5/containers/introducing-the-host-compute-service-hcs/ba-p/382332) (HCS) to launch and manage [Windows Containers](https://docs.microsoft.com/en-us/virtualization/windowscontainers/about/). It also contains other helpers and functions for managing Windows Containers such as the Golang interface for the Host Network Service (HNS), as well as code for the [guest agent](./internal/guest/README.md) (commonly referred to as the GCS or Guest Compute Service in the codebase) used to support running Linux Hyper-V containers.
|
||||
|
||||
It is primarily used in the [Moby Project](https://github.com/moby/moby), but it can be freely used by other projects as well.
|
||||
It is primarily used in the [Moby](https://github.com/moby/moby) and [Containerd](https://github.com/containerd/containerd) projects, but it can be freely used by other projects as well.
|
||||
|
||||
## Building
|
||||
|
||||
While this repository can be used as a library of sorts to call the HCS apis, there are a couple binaries built out of the repository as well. The main ones being the Linux guest agent, and an implementation of the [runtime v2 containerd shim api](https://github.com/containerd/containerd/blob/master/runtime/v2/README.md).
|
||||
### Linux Hyper-V Container Guest Agent
|
||||
|
||||
To build the Linux guest agent itself all that's needed is to set your GOOS to "Linux" and build out of ./cmd/gcs.
|
||||
```powershell
|
||||
C:\> $env:GOOS="linux"
|
||||
C:\> go build .\cmd\gcs\
|
||||
```
|
||||
|
||||
or on a Linux machine
|
||||
```sh
|
||||
> go build ./cmd/gcs
|
||||
```
|
||||
|
||||
If you want it to be packaged inside of a rootfs to boot with alongside all of the other tools then you'll need to provide a rootfs that it can be packaged inside of. An easy way is to export the rootfs of a container.
|
||||
|
||||
```sh
|
||||
docker pull busybox
|
||||
docker run --name base_image_container busybox
|
||||
docker export base_image_container | gzip > base.tar.gz
|
||||
BASE=./base.tar.gz
|
||||
make all
|
||||
```
|
||||
|
||||
If the build is successful, in the `./out` folder you should see:
|
||||
```sh
|
||||
> ls ./out/
|
||||
delta.tar.gz initrd.img rootfs.tar.gz
|
||||
```
|
||||
|
||||
### Containerd Shim
|
||||
For info on the Runtime V2 API: https://github.com/containerd/containerd/blob/master/runtime/v2/README.md.
|
||||
|
||||
Contrary to the typical Linux architecture of shim -> runc, the runhcs shim is used both to launch and manage the lifetime of containers.
|
||||
|
||||
```powershell
|
||||
C:\> $env:GOOS="windows"
|
||||
C:\> go build .\cmd\containerd-shim-runhcs-v1
|
||||
```
|
||||
|
||||
Then place the binary in the same directory that Containerd is located at in your environment. A default Containerd configuration file can be generated by running:
|
||||
```powershell
|
||||
.\containerd.exe config default | Out-File "C:\Program Files\containerd\config.toml" -Encoding ascii
|
||||
```
|
||||
|
||||
This config file will already have the shim set as the default runtime for cri interactions.
|
||||
|
||||
To trial using the shim out with ctr.exe:
|
||||
```powershell
|
||||
C:\> ctr.exe run --runtime io.containerd.runhcs.v1 --rm mcr.microsoft.com/windows/nanoserver:2004 windows-test cmd /c "echo Hello World!"
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
|
||||
@@ -16,7 +70,27 @@ When you submit a pull request, a CLA-bot will automatically determine whether y
|
||||
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
We also ask that contributors [sign their commits](https://git-scm.com/docs/git-commit) using `git commit -s` or `git commit --signoff` to certify they either authored the work themselves or otherwise have permission to use it in this project.
|
||||
We also require that contributors [sign their commits](https://git-scm.com/docs/git-commit) using `git commit -s` or `git commit --signoff` to
|
||||
certify they either authored the work themselves or otherwise have permission to use it in this project. Please see https://developercertificate.org/ for
|
||||
more info, as well as to make sure that you can attest to the rules listed. Our CI uses the [DCO Github app](https://github.com/apps/dco) to ensure
|
||||
that all commits in a given PR are signed-off.
|
||||
|
||||
### Test Directory (Important to note)
|
||||
|
||||
This project has tried to trim some dependencies from the root Go modules file that would be cumbersome to get transitively included if this
|
||||
project is being vendored/used as a library. Some of these dependencies were only being used for tests, so the /test directory in this project also has
|
||||
its own go.mod file where these are now included to get around this issue. Our tests rely on the code in this project to run, so the test Go modules file
|
||||
has a relative path replace directive to pull in the latest hcsshim code that the tests actually touch from this project
|
||||
(which is the repo itself on your disk).
|
||||
|
||||
```
|
||||
replace (
|
||||
github.com/Microsoft/hcsshim => ../
|
||||
)
|
||||
```
|
||||
|
||||
Because of this, for most code changes you may need to run `go mod vendor` + `go mod tidy` in the /test directory in this repository, as the
|
||||
CI in this project will check if the files are out of date and will fail if this is true.
|
||||
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
29
vendor/github.com/Microsoft/hcsshim/go.mod
generated
vendored
29
vendor/github.com/Microsoft/hcsshim/go.mod
generated
vendored
@@ -3,25 +3,34 @@ module github.com/Microsoft/hcsshim
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/Microsoft/go-winio v0.4.17
|
||||
github.com/cenkalti/backoff/v4 v4.1.1
|
||||
github.com/containerd/cgroups v1.0.1
|
||||
github.com/containerd/console v1.0.2
|
||||
github.com/containerd/containerd v1.4.9
|
||||
github.com/containerd/continuity v0.1.0 // indirect
|
||||
github.com/containerd/fifo v1.0.0 // indirect
|
||||
github.com/containerd/containerd v1.5.7
|
||||
github.com/containerd/go-runc v1.0.0
|
||||
github.com/containerd/ttrpc v1.0.2
|
||||
github.com/containerd/ttrpc v1.1.0
|
||||
github.com/containerd/typeurl v1.0.2
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/go-cmp v0.5.6
|
||||
github.com/google/go-containerregistry v0.5.1
|
||||
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3
|
||||
github.com/mattn/go-shellwords v1.0.6
|
||||
github.com/opencontainers/runc v1.0.2
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/urfave/cli v1.22.2
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
go.opencensus.io v0.22.3
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492
|
||||
google.golang.org/grpc v1.33.2
|
||||
gotest.tools/v3 v3.0.3 // indirect
|
||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
|
||||
google.golang.org/grpc v1.40.0
|
||||
)
|
||||
|
||||
replace (
|
||||
|
||||
784
vendor/github.com/Microsoft/hcsshim/go.sum
generated
vendored
784
vendor/github.com/Microsoft/hcsshim/go.sum
generated
vendored
File diff suppressed because it is too large
Load Diff
16
vendor/github.com/Microsoft/hcsshim/internal/hcs/errors.go
generated
vendored
16
vendor/github.com/Microsoft/hcsshim/internal/hcs/errors.go
generated
vendored
@@ -78,6 +78,13 @@ var (
|
||||
|
||||
// ErrNotSupported is an error encountered when hcs doesn't support the request
|
||||
ErrPlatformNotSupported = errors.New("unsupported platform request")
|
||||
|
||||
// ErrProcessAlreadyStopped is returned by hcs if the process we're trying to kill has already been stopped.
|
||||
ErrProcessAlreadyStopped = syscall.Errno(0x8037011f)
|
||||
|
||||
// ErrInvalidHandle is an error that can be encountrered when querying the properties of a compute system when the handle to that
|
||||
// compute system has already been closed.
|
||||
ErrInvalidHandle = syscall.Errno(0x6)
|
||||
)
|
||||
|
||||
type ErrorEvent struct {
|
||||
@@ -249,6 +256,14 @@ func IsNotExist(err error) bool {
|
||||
err == ErrElementNotFound
|
||||
}
|
||||
|
||||
// IsErrorInvalidHandle checks whether the error is the result of an operation carried
|
||||
// out on a handle that is invalid/closed. This error popped up while trying to query
|
||||
// stats on a container in the process of being stopped.
|
||||
func IsErrorInvalidHandle(err error) bool {
|
||||
err = getInnerError(err)
|
||||
return err == ErrInvalidHandle
|
||||
}
|
||||
|
||||
// IsAlreadyClosed checks if an error is caused by the Container or Process having been
|
||||
// already closed by a call to the Close() method.
|
||||
func IsAlreadyClosed(err error) bool {
|
||||
@@ -281,6 +296,7 @@ func IsTimeout(err error) bool {
|
||||
func IsAlreadyStopped(err error) bool {
|
||||
err = getInnerError(err)
|
||||
return err == ErrVmcomputeAlreadyStopped ||
|
||||
err == ErrProcessAlreadyStopped ||
|
||||
err == ErrElementNotFound
|
||||
}
|
||||
|
||||
|
||||
56
vendor/github.com/Microsoft/hcsshim/internal/hcs/process.go
generated
vendored
56
vendor/github.com/Microsoft/hcsshim/internal/hcs/process.go
generated
vendored
@@ -3,7 +3,9 @@ package hcs
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -16,16 +18,17 @@ import (
|
||||
|
||||
// ContainerError is an error encountered in HCS
|
||||
type Process struct {
|
||||
handleLock sync.RWMutex
|
||||
handle vmcompute.HcsProcess
|
||||
processID int
|
||||
system *System
|
||||
hasCachedStdio bool
|
||||
stdioLock sync.Mutex
|
||||
stdin io.WriteCloser
|
||||
stdout io.ReadCloser
|
||||
stderr io.ReadCloser
|
||||
callbackNumber uintptr
|
||||
handleLock sync.RWMutex
|
||||
handle vmcompute.HcsProcess
|
||||
processID int
|
||||
system *System
|
||||
hasCachedStdio bool
|
||||
stdioLock sync.Mutex
|
||||
stdin io.WriteCloser
|
||||
stdout io.ReadCloser
|
||||
stderr io.ReadCloser
|
||||
callbackNumber uintptr
|
||||
killSignalDelivered bool
|
||||
|
||||
closedWaitOnce sync.Once
|
||||
waitBlock chan struct{}
|
||||
@@ -149,12 +152,45 @@ func (process *Process) Kill(ctx context.Context) (bool, error) {
|
||||
return false, makeProcessError(process, operation, ErrAlreadyClosed, nil)
|
||||
}
|
||||
|
||||
if process.killSignalDelivered {
|
||||
// A kill signal has already been sent to this process. Sending a second
|
||||
// one offers no real benefit, as processes cannot stop themselves from
|
||||
// being terminated, once a TerminateProcess has been issued. Sending a
|
||||
// second kill may result in a number of errors (two of which detailed bellow)
|
||||
// and which we can avoid handling.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
resultJSON, err := vmcompute.HcsTerminateProcess(ctx, process.handle)
|
||||
if err != nil {
|
||||
// We still need to check these two cases, as processes may still be killed by an
|
||||
// external actor (human operator, OOM, random script etc).
|
||||
if errors.Is(err, os.ErrPermission) || IsAlreadyStopped(err) {
|
||||
// There are two cases where it should be safe to ignore an error returned
|
||||
// by HcsTerminateProcess. The first one is cause by the fact that
|
||||
// HcsTerminateProcess ends up calling TerminateProcess in the context
|
||||
// of a container. According to the TerminateProcess documentation:
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess#remarks
|
||||
// After a process has terminated, call to TerminateProcess with open
|
||||
// handles to the process fails with ERROR_ACCESS_DENIED (5) error code.
|
||||
// It's safe to ignore this error here. HCS should always have permissions
|
||||
// to kill processes inside any container. So an ERROR_ACCESS_DENIED
|
||||
// is unlikely to be anything else than what the ending remarks in the
|
||||
// documentation states.
|
||||
//
|
||||
// The second case is generated by hcs itself, if for any reason HcsTerminateProcess
|
||||
// is called twice in a very short amount of time. In such cases, hcs may return
|
||||
// HCS_E_PROCESS_ALREADY_STOPPED.
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
delivered, err := process.processSignalResult(ctx, err)
|
||||
if err != nil {
|
||||
err = makeProcessError(process, operation, err, events)
|
||||
}
|
||||
|
||||
process.killSignalDelivered = delivered
|
||||
return delivered, err
|
||||
}
|
||||
|
||||
|
||||
6
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/attachment.go
generated
vendored
6
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/attachment.go
generated
vendored
@@ -27,4 +27,10 @@ type Attachment struct {
|
||||
CaptureIoAttributionContext bool `json:"CaptureIoAttributionContext,omitempty"`
|
||||
|
||||
ReadOnly bool `json:"ReadOnly,omitempty"`
|
||||
|
||||
SupportCompressedVolumes bool `json:"SupportCompressedVolumes,omitempty"`
|
||||
|
||||
AlwaysAllowSparseFiles bool `json:"AlwaysAllowSparseFiles,omitempty"`
|
||||
|
||||
ExtensibleVirtualDiskType string `json:"ExtensibleVirtualDiskType,omitempty"`
|
||||
}
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/container.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/container.go
generated
vendored
@@ -31,4 +31,6 @@ type Container struct {
|
||||
RegistryChanges *RegistryChanges `json:"RegistryChanges,omitempty"`
|
||||
|
||||
AssignedDevices []Device `json:"AssignedDevices,omitempty"`
|
||||
|
||||
AdditionalDeviceNamespace *ContainerDefinitionDevice `json:"AdditionalDeviceNamespace,omitempty"`
|
||||
}
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/cpu_group_config.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/cpu_group_config.go
generated
vendored
@@ -14,5 +14,5 @@ type CpuGroupConfig struct {
|
||||
Affinity *CpuGroupAffinity `json:"Affinity,omitempty"`
|
||||
GroupProperties []CpuGroupProperty `json:"GroupProperties,omitempty"`
|
||||
// Hypervisor CPU group IDs exposed to clients
|
||||
HypervisorGroupId int32 `json:"HypervisorGroupId,omitempty"`
|
||||
HypervisorGroupId uint64 `json:"HypervisorGroupId,omitempty"`
|
||||
}
|
||||
|
||||
8
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/device.go
generated
vendored
8
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/device.go
generated
vendored
@@ -12,9 +12,9 @@ package hcsschema
|
||||
type DeviceType string
|
||||
|
||||
const (
|
||||
ClassGUID DeviceType = "ClassGuid"
|
||||
DeviceInstance DeviceType = "DeviceInstance"
|
||||
GPUMirror DeviceType = "GpuMirror"
|
||||
ClassGUID DeviceType = "ClassGuid"
|
||||
DeviceInstanceID DeviceType = "DeviceInstance"
|
||||
GPUMirror DeviceType = "GpuMirror"
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
@@ -22,6 +22,6 @@ type Device struct {
|
||||
Type DeviceType `json:"Type,omitempty"`
|
||||
// The interface class guid of the device interfaces to assign to the container. Only used when Type is ClassGuid.
|
||||
InterfaceClassGuid string `json:"InterfaceClassGuid,omitempty"`
|
||||
// The location path of the device to assign to the container. Only used when Type is DeviceInstance.
|
||||
// The location path of the device to assign to the container. Only used when Type is DeviceInstanceID.
|
||||
LocationPath string `json:"LocationPath,omitempty"`
|
||||
}
|
||||
|
||||
14
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_container_definition_device.go
generated
vendored
Normal file
14
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_container_definition_device.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* HCS API
|
||||
*
|
||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
||||
*
|
||||
* API version: 2.4
|
||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
||||
*/
|
||||
|
||||
package hcsschema
|
||||
|
||||
type ContainerDefinitionDevice struct {
|
||||
DeviceExtension []DeviceExtension `json:"device_extension,omitempty"`
|
||||
}
|
||||
15
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_device_category.go
generated
vendored
Normal file
15
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_device_category.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* HCS API
|
||||
*
|
||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
||||
*
|
||||
* API version: 2.4
|
||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
||||
*/
|
||||
|
||||
package hcsschema
|
||||
|
||||
type DeviceCategory struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
InterfaceClass []InterfaceClass `json:"interface_class,omitempty"`
|
||||
}
|
||||
15
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_device_extension.go
generated
vendored
Normal file
15
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_device_extension.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* HCS API
|
||||
*
|
||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
||||
*
|
||||
* API version: 2.4
|
||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
||||
*/
|
||||
|
||||
package hcsschema
|
||||
|
||||
type DeviceExtension struct {
|
||||
DeviceCategory *DeviceCategory `json:"device_category,omitempty"`
|
||||
Namespace *DeviceExtensionNamespace `json:"namespace,omitempty"`
|
||||
}
|
||||
17
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_device_instance.go
generated
vendored
Normal file
17
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_device_instance.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* HCS API
|
||||
*
|
||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
||||
*
|
||||
* API version: 2.4
|
||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
||||
*/
|
||||
|
||||
package hcsschema
|
||||
|
||||
type DeviceInstance struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
LocationPath string `json:"location_path,omitempty"`
|
||||
PortName string `json:"port_name,omitempty"`
|
||||
InterfaceClass []InterfaceClass `json:"interface_class,omitempty"`
|
||||
}
|
||||
16
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_device_namespace.go
generated
vendored
Normal file
16
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_device_namespace.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* HCS API
|
||||
*
|
||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
||||
*
|
||||
* API version: 2.4
|
||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
||||
*/
|
||||
|
||||
package hcsschema
|
||||
|
||||
type DeviceNamespace struct {
|
||||
RequiresDriverstore bool `json:"requires_driverstore,omitempty"`
|
||||
DeviceCategory []DeviceCategory `json:"device_category,omitempty"`
|
||||
DeviceInstance []DeviceInstance `json:"device_instance,omitempty"`
|
||||
}
|
||||
16
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_interface_class.go
generated
vendored
Normal file
16
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_interface_class.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* HCS API
|
||||
*
|
||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
||||
*
|
||||
* API version: 2.4
|
||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
||||
*/
|
||||
|
||||
package hcsschema
|
||||
|
||||
type InterfaceClass struct {
|
||||
Type_ string `json:"type,omitempty"`
|
||||
Identifier string `json:"identifier,omitempty"`
|
||||
Recurse bool `json:"recurse,omitempty"`
|
||||
}
|
||||
15
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_namespace.go
generated
vendored
Normal file
15
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_namespace.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* HCS API
|
||||
*
|
||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
||||
*
|
||||
* API version: 2.4
|
||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
||||
*/
|
||||
|
||||
package hcsschema
|
||||
|
||||
type DeviceExtensionNamespace struct {
|
||||
Ob *ObjectNamespace `json:"ob,omitempty"`
|
||||
Device *DeviceNamespace `json:"device,omitempty"`
|
||||
}
|
||||
18
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_object_directory.go
generated
vendored
Normal file
18
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_object_directory.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* HCS API
|
||||
*
|
||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
||||
*
|
||||
* API version: 2.4
|
||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
||||
*/
|
||||
|
||||
package hcsschema
|
||||
|
||||
type ObjectDirectory struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Clonesd string `json:"clonesd,omitempty"`
|
||||
Shadow string `json:"shadow,omitempty"`
|
||||
Symlink []ObjectSymlink `json:"symlink,omitempty"`
|
||||
Objdir []ObjectDirectory `json:"objdir,omitempty"`
|
||||
}
|
||||
16
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_object_namespace.go
generated
vendored
Normal file
16
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_object_namespace.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* HCS API
|
||||
*
|
||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
||||
*
|
||||
* API version: 2.4
|
||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
||||
*/
|
||||
|
||||
package hcsschema
|
||||
|
||||
type ObjectNamespace struct {
|
||||
Shadow string `json:"shadow,omitempty"`
|
||||
Symlink []ObjectSymlink `json:"symlink,omitempty"`
|
||||
Objdir []ObjectDirectory `json:"objdir,omitempty"`
|
||||
}
|
||||
18
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_object_symlink.go
generated
vendored
Normal file
18
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/model_object_symlink.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* HCS API
|
||||
*
|
||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
||||
*
|
||||
* API version: 2.4
|
||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
||||
*/
|
||||
|
||||
package hcsschema
|
||||
|
||||
type ObjectSymlink struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
Pathtoclone string `json:"pathtoclone,omitempty"`
|
||||
AccessMask int32 `json:"access_mask,omitempty"`
|
||||
}
|
||||
15
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/virtual_p_mem_mapping.go
generated
vendored
Normal file
15
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/virtual_p_mem_mapping.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* HCS API
|
||||
*
|
||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
||||
*
|
||||
* API version: 2.4
|
||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
||||
*/
|
||||
|
||||
package hcsschema
|
||||
|
||||
type VirtualPMemMapping struct {
|
||||
HostPath string `json:"HostPath,omitempty"`
|
||||
ImageFormat string `json:"ImageFormat,omitempty"`
|
||||
}
|
||||
1
vendor/github.com/Microsoft/hcsshim/internal/hns/hnsendpoint.go
generated
vendored
1
vendor/github.com/Microsoft/hcsshim/internal/hns/hnsendpoint.go
generated
vendored
@@ -20,6 +20,7 @@ type HNSEndpoint struct {
|
||||
IPv6Address net.IP `json:",omitempty"`
|
||||
DNSSuffix string `json:",omitempty"`
|
||||
DNSServerList string `json:",omitempty"`
|
||||
DNSDomain string `json:",omitempty"`
|
||||
GatewayAddress string `json:",omitempty"`
|
||||
GatewayAddressV6 string `json:",omitempty"`
|
||||
EnableInternalDNS bool `json:",omitempty"`
|
||||
|
||||
30
vendor/github.com/Microsoft/hcsshim/internal/hns/hnspolicy.go
generated
vendored
30
vendor/github.com/Microsoft/hcsshim/internal/hns/hnspolicy.go
generated
vendored
@@ -22,9 +22,9 @@ const (
|
||||
|
||||
type NatPolicy struct {
|
||||
Type PolicyType `json:"Type"`
|
||||
Protocol string
|
||||
InternalPort uint16
|
||||
ExternalPort uint16
|
||||
Protocol string `json:",omitempty"`
|
||||
InternalPort uint16 `json:",omitempty"`
|
||||
ExternalPort uint16 `json:",omitempty"`
|
||||
}
|
||||
|
||||
type QosPolicy struct {
|
||||
@@ -88,20 +88,20 @@ const (
|
||||
type ACLPolicy struct {
|
||||
Type PolicyType `json:"Type"`
|
||||
Id string `json:"Id,omitempty"`
|
||||
Protocol uint16
|
||||
Protocols string `json:"Protocols,omitempty"`
|
||||
InternalPort uint16
|
||||
Protocol uint16 `json:",omitempty"`
|
||||
Protocols string `json:"Protocols,omitempty"`
|
||||
InternalPort uint16 `json:",omitempty"`
|
||||
Action ActionType
|
||||
Direction DirectionType
|
||||
LocalAddresses string
|
||||
RemoteAddresses string
|
||||
LocalPorts string `json:"LocalPorts,omitempty"`
|
||||
LocalPort uint16
|
||||
RemotePorts string `json:"RemotePorts,omitempty"`
|
||||
RemotePort uint16
|
||||
RuleType RuleType `json:"RuleType,omitempty"`
|
||||
Priority uint16
|
||||
ServiceName string
|
||||
LocalAddresses string `json:",omitempty"`
|
||||
RemoteAddresses string `json:",omitempty"`
|
||||
LocalPorts string `json:"LocalPorts,omitempty"`
|
||||
LocalPort uint16 `json:",omitempty"`
|
||||
RemotePorts string `json:"RemotePorts,omitempty"`
|
||||
RemotePort uint16 `json:",omitempty"`
|
||||
RuleType RuleType `json:"RuleType,omitempty"`
|
||||
Priority uint16 `json:",omitempty"`
|
||||
ServiceName string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/activatelayer.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/activatelayer.go
generated
vendored
@@ -21,7 +21,7 @@ func ActivateLayer(ctx context.Context, path string) (err error) {
|
||||
|
||||
err = activateLayer(&stdDriverInfo, path)
|
||||
if err != nil {
|
||||
return hcserror.New(err, title+" - failed", "")
|
||||
return hcserror.New(err, title, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/createlayer.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/createlayer.go
generated
vendored
@@ -21,7 +21,7 @@ func CreateLayer(ctx context.Context, path, parent string) (err error) {
|
||||
|
||||
err = createLayer(&stdDriverInfo, path, parent)
|
||||
if err != nil {
|
||||
return hcserror.New(err, title+" - failed", "")
|
||||
return hcserror.New(err, title, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/createscratchlayer.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/createscratchlayer.go
generated
vendored
@@ -28,7 +28,7 @@ func CreateScratchLayer(ctx context.Context, path string, parentLayerPaths []str
|
||||
|
||||
err = createSandboxLayer(&stdDriverInfo, path, 0, layers)
|
||||
if err != nil {
|
||||
return hcserror.New(err, title+" - failed", "")
|
||||
return hcserror.New(err, title, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/destroylayer.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/destroylayer.go
generated
vendored
@@ -19,7 +19,7 @@ func DestroyLayer(ctx context.Context, path string) (err error) {
|
||||
|
||||
err = destroyLayer(&stdDriverInfo, path)
|
||||
if err != nil {
|
||||
return hcserror.New(err, title+" - failed", "")
|
||||
return hcserror.New(err, title, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/expandscratchsize.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/expandscratchsize.go
generated
vendored
@@ -25,7 +25,7 @@ func ExpandScratchSize(ctx context.Context, path string, size uint64) (err error
|
||||
|
||||
err = expandSandboxSize(&stdDriverInfo, path, size)
|
||||
if err != nil {
|
||||
return hcserror.New(err, title+" - failed", "")
|
||||
return hcserror.New(err, title, "")
|
||||
}
|
||||
|
||||
// Manually expand the volume now in order to work around bugs in 19H1 and
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/exportlayer.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/exportlayer.go
generated
vendored
@@ -35,7 +35,7 @@ func ExportLayer(ctx context.Context, path string, exportFolderPath string, pare
|
||||
|
||||
err = exportLayer(&stdDriverInfo, path, exportFolderPath, layers)
|
||||
if err != nil {
|
||||
return hcserror.New(err, title+" - failed", "")
|
||||
return hcserror.New(err, title, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
4
vendor/github.com/Microsoft/hcsshim/internal/wclayer/getlayermountpath.go
generated
vendored
4
vendor/github.com/Microsoft/hcsshim/internal/wclayer/getlayermountpath.go
generated
vendored
@@ -27,7 +27,7 @@ func GetLayerMountPath(ctx context.Context, path string) (_ string, err error) {
|
||||
log.G(ctx).Debug("Calling proc (1)")
|
||||
err = getLayerMountPath(&stdDriverInfo, path, &mountPathLength, nil)
|
||||
if err != nil {
|
||||
return "", hcserror.New(err, title+" - failed", "(first call)")
|
||||
return "", hcserror.New(err, title, "(first call)")
|
||||
}
|
||||
|
||||
// Allocate a mount path of the returned length.
|
||||
@@ -41,7 +41,7 @@ func GetLayerMountPath(ctx context.Context, path string) (_ string, err error) {
|
||||
log.G(ctx).Debug("Calling proc (2)")
|
||||
err = getLayerMountPath(&stdDriverInfo, path, &mountPathLength, &mountPathp[0])
|
||||
if err != nil {
|
||||
return "", hcserror.New(err, title+" - failed", "(second call)")
|
||||
return "", hcserror.New(err, title, "(second call)")
|
||||
}
|
||||
|
||||
mountPath := syscall.UTF16ToString(mountPathp[0:])
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/getsharedbaseimages.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/getsharedbaseimages.go
generated
vendored
@@ -21,7 +21,7 @@ func GetSharedBaseImages(ctx context.Context) (_ string, err error) {
|
||||
var buffer *uint16
|
||||
err = getBaseImages(&buffer)
|
||||
if err != nil {
|
||||
return "", hcserror.New(err, title+" - failed", "")
|
||||
return "", hcserror.New(err, title, "")
|
||||
}
|
||||
imageData := interop.ConvertAndFreeCoTaskMemString(buffer)
|
||||
span.AddAttributes(trace.StringAttribute("imageData", imageData))
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/grantvmaccess.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/grantvmaccess.go
generated
vendored
@@ -20,7 +20,7 @@ func GrantVmAccess(ctx context.Context, vmid string, filepath string) (err error
|
||||
|
||||
err = grantVmAccess(vmid, filepath)
|
||||
if err != nil {
|
||||
return hcserror.New(err, title+" - failed", "")
|
||||
return hcserror.New(err, title, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/importlayer.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/importlayer.go
generated
vendored
@@ -36,7 +36,7 @@ func ImportLayer(ctx context.Context, path string, importFolderPath string, pare
|
||||
|
||||
err = importLayer(&stdDriverInfo, path, importFolderPath, layers)
|
||||
if err != nil {
|
||||
return hcserror.New(err, title+" - failed", "")
|
||||
return hcserror.New(err, title, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/layerexists.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/layerexists.go
generated
vendored
@@ -21,7 +21,7 @@ func LayerExists(ctx context.Context, path string) (_ bool, err error) {
|
||||
var exists uint32
|
||||
err = layerExists(&stdDriverInfo, path, &exists)
|
||||
if err != nil {
|
||||
return false, hcserror.New(err, title+" - failed", "")
|
||||
return false, hcserror.New(err, title, "")
|
||||
}
|
||||
span.AddAttributes(trace.BoolAttribute("layer-exists", exists != 0))
|
||||
return exists != 0, nil
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/legacy.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/legacy.go
generated
vendored
@@ -76,7 +76,7 @@ func readTombstones(path string) (map[string]([]string), error) {
|
||||
defer tf.Close()
|
||||
s := bufio.NewScanner(tf)
|
||||
if !s.Scan() || s.Text() != "\xef\xbb\xbfVersion 1.0" {
|
||||
return nil, errors.New("Invalid tombstones file")
|
||||
return nil, errors.New("invalid tombstones file")
|
||||
}
|
||||
|
||||
ts := make(map[string]([]string))
|
||||
|
||||
4
vendor/github.com/Microsoft/hcsshim/internal/wclayer/nametoguid.go
generated
vendored
4
vendor/github.com/Microsoft/hcsshim/internal/wclayer/nametoguid.go
generated
vendored
@@ -17,12 +17,12 @@ func NameToGuid(ctx context.Context, name string) (_ guid.GUID, err error) {
|
||||
ctx, span := trace.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(trace.StringAttribute("name", name))
|
||||
span.AddAttributes(trace.StringAttribute("objectName", name))
|
||||
|
||||
var id guid.GUID
|
||||
err = nameToGuid(name, &id)
|
||||
if err != nil {
|
||||
return guid.GUID{}, hcserror.New(err, title+" - failed", "")
|
||||
return guid.GUID{}, hcserror.New(err, title, "")
|
||||
}
|
||||
span.AddAttributes(trace.StringAttribute("guid", id.String()))
|
||||
return id, nil
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/preparelayer.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/preparelayer.go
generated
vendored
@@ -38,7 +38,7 @@ func PrepareLayer(ctx context.Context, path string, parentLayerPaths []string) (
|
||||
defer prepareLayerLock.Unlock()
|
||||
err = prepareLayer(&stdDriverInfo, path, layers)
|
||||
if err != nil {
|
||||
return hcserror.New(err, title+" - failed", "")
|
||||
return hcserror.New(err, title, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/unpreparelayer.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/wclayer/unpreparelayer.go
generated
vendored
@@ -19,7 +19,7 @@ func UnprepareLayer(ctx context.Context, path string) (err error) {
|
||||
|
||||
err = unprepareLayer(&stdDriverInfo, path)
|
||||
if err != nil {
|
||||
return hcserror.New(err, title+" - failed", "")
|
||||
return hcserror.New(err, title, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
44
vendor/github.com/Microsoft/hcsshim/internal/winapi/console.go
generated
vendored
Normal file
44
vendor/github.com/Microsoft/hcsshim/internal/winapi/console.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
package winapi
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const PSEUDOCONSOLE_INHERIT_CURSOR = 0x1
|
||||
|
||||
// CreatePseudoConsole creates a windows pseudo console.
|
||||
func CreatePseudoConsole(size windows.Coord, hInput windows.Handle, hOutput windows.Handle, dwFlags uint32, hpcon *windows.Handle) error {
|
||||
// We need this wrapper as the function takes a COORD struct and not a pointer to one, so we need to cast to something beforehand.
|
||||
return createPseudoConsole(*((*uint32)(unsafe.Pointer(&size))), hInput, hOutput, 0, hpcon)
|
||||
}
|
||||
|
||||
// ResizePseudoConsole resizes the internal buffers of the pseudo console to the width and height specified in `size`.
|
||||
func ResizePseudoConsole(hpcon windows.Handle, size windows.Coord) error {
|
||||
// We need this wrapper as the function takes a COORD struct and not a pointer to one, so we need to cast to something beforehand.
|
||||
return resizePseudoConsole(hpcon, *((*uint32)(unsafe.Pointer(&size))))
|
||||
}
|
||||
|
||||
// HRESULT WINAPI CreatePseudoConsole(
|
||||
// _In_ COORD size,
|
||||
// _In_ HANDLE hInput,
|
||||
// _In_ HANDLE hOutput,
|
||||
// _In_ DWORD dwFlags,
|
||||
// _Out_ HPCON* phPC
|
||||
// );
|
||||
//
|
||||
//sys createPseudoConsole(size uint32, hInput windows.Handle, hOutput windows.Handle, dwFlags uint32, hpcon *windows.Handle) (hr error) = kernel32.CreatePseudoConsole
|
||||
|
||||
// void WINAPI ClosePseudoConsole(
|
||||
// _In_ HPCON hPC
|
||||
// );
|
||||
//
|
||||
//sys ClosePseudoConsole(hpc windows.Handle) = kernel32.ClosePseudoConsole
|
||||
|
||||
// HRESULT WINAPI ResizePseudoConsole(
|
||||
// _In_ HPCON hPC ,
|
||||
// _In_ COORD size
|
||||
// );
|
||||
//
|
||||
//sys resizePseudoConsole(hPc windows.Handle, size uint32) (hr error) = kernel32.ResizePseudoConsole
|
||||
23
vendor/github.com/Microsoft/hcsshim/internal/winapi/memory.go
generated
vendored
23
vendor/github.com/Microsoft/hcsshim/internal/winapi/memory.go
generated
vendored
@@ -1,27 +1,4 @@
|
||||
package winapi
|
||||
|
||||
// VOID RtlMoveMemory(
|
||||
// _Out_ VOID UNALIGNED *Destination,
|
||||
// _In_ const VOID UNALIGNED *Source,
|
||||
// _In_ SIZE_T Length
|
||||
// );
|
||||
//sys RtlMoveMemory(destination *byte, source *byte, length uintptr) (err error) = kernel32.RtlMoveMemory
|
||||
|
||||
//sys LocalAlloc(flags uint32, size int) (ptr uintptr) = kernel32.LocalAlloc
|
||||
//sys LocalFree(ptr uintptr) = kernel32.LocalFree
|
||||
|
||||
// BOOL QueryWorkingSet(
|
||||
// HANDLE hProcess,
|
||||
// PVOID pv,
|
||||
// DWORD cb
|
||||
// );
|
||||
//sys QueryWorkingSet(handle windows.Handle, pv uintptr, cb uint32) (err error) = psapi.QueryWorkingSet
|
||||
|
||||
type PSAPI_WORKING_SET_INFORMATION struct {
|
||||
NumberOfEntries uintptr
|
||||
WorkingSetInfo [1]PSAPI_WORKING_SET_BLOCK
|
||||
}
|
||||
|
||||
type PSAPI_WORKING_SET_BLOCK struct {
|
||||
Flags uintptr
|
||||
}
|
||||
|
||||
10
vendor/github.com/Microsoft/hcsshim/internal/winapi/process.go
generated
vendored
10
vendor/github.com/Microsoft/hcsshim/internal/winapi/process.go
generated
vendored
@@ -2,9 +2,7 @@ package winapi
|
||||
|
||||
const PROCESS_ALL_ACCESS uint32 = 2097151
|
||||
|
||||
// DWORD GetProcessImageFileNameW(
|
||||
// HANDLE hProcess,
|
||||
// LPWSTR lpImageFileName,
|
||||
// DWORD nSize
|
||||
// );
|
||||
//sys GetProcessImageFileName(hProcess windows.Handle, imageFileName *uint16, nSize uint32) (size uint32, err error) = kernel32.GetProcessImageFileNameW
|
||||
const (
|
||||
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x20016
|
||||
PROC_THREAD_ATTRIBUTE_JOB_LIST = 0x2000D
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user