Merge pull request #6207 from vrothberg/auth-header

add X-Registry-Auth header support
This commit is contained in:
OpenShift Merge Robot 2020-05-29 10:37:59 -04:00 committed by GitHub
commit 0c750a9672
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
108 changed files with 2582 additions and 612 deletions

View File

@ -8,6 +8,7 @@ import (
"github.com/containers/image/v5/types"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -17,7 +18,8 @@ import (
// CLI-only fields into the API types.
type pullOptionsWrapper struct {
entities.ImagePullOptions
TLSVerifyCLI bool // CLI only
TLSVerifyCLI bool // CLI only
CredentialsCLI string
}
var (
@ -77,7 +79,7 @@ func pullFlags(flags *pflag.FlagSet) {
flags.BoolVar(&pullOptions.AllTags, "all-tags", false, "All tagged images in the repository will be pulled")
flags.StringVar(&pullOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
flags.StringVar(&pullOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys")
flags.StringVar(&pullOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry")
flags.StringVar(&pullOptions.CredentialsCLI, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry")
flags.StringVar(&pullOptions.OverrideArch, "override-arch", "", "Use `ARCH` instead of the architecture of the machine for choosing images")
flags.StringVar(&pullOptions.OverrideOS, "override-os", "", "Use `OS` instead of the running OS for choosing images")
flags.BoolVarP(&pullOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images")
@ -107,6 +109,14 @@ func imagePull(cmd *cobra.Command, args []string) error {
}
}
if pullOptions.CredentialsCLI != "" {
creds, err := util.ParseRegistryCreds(pullOptions.CredentialsCLI)
if err != nil {
return err
}
pullOptions.Username = creds.Username
pullOptions.Password = creds.Password
}
// Let's do all the remaining Yoga in the API to prevent us from
// scattering logic across (too) many parts of the code.
pullReport, err := registry.ImageEngine().Pull(registry.GetContext(), args[0], pullOptions.ImagePullOptions)

View File

@ -7,6 +7,7 @@ import (
"github.com/containers/image/v5/types"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -16,7 +17,8 @@ import (
// CLI-only fields into the API types.
type pushOptionsWrapper struct {
entities.ImagePushOptions
TLSVerifyCLI bool // CLI only
TLSVerifyCLI bool // CLI only
CredentialsCLI string
}
var (
@ -73,7 +75,7 @@ func pushFlags(flags *pflag.FlagSet) {
flags.StringVar(&pushOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
flags.StringVar(&pushOptions.CertDir, "cert-dir", "", "Path to a directory containing TLS certificates and keys")
flags.BoolVar(&pushOptions.Compress, "compress", false, "Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type as source)")
flags.StringVar(&pushOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry")
flags.StringVar(&pushOptions.CredentialsCLI, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry")
flags.StringVar(&pushOptions.DigestFile, "digestfile", "", "Write the digest of the pushed image to the specified file")
flags.StringVarP(&pushOptions.Format, "format", "f", "", "Manifest type (oci, v2s1, or v2s2) to use when pushing an image using the 'dir' transport (default is manifest type of source)")
flags.BoolVarP(&pushOptions.Quiet, "quiet", "q", false, "Suppress output information when pushing images")
@ -122,6 +124,15 @@ func imagePush(cmd *cobra.Command, args []string) error {
}
}
if pushOptions.CredentialsCLI != "" {
creds, err := util.ParseRegistryCreds(pushOptions.CredentialsCLI)
if err != nil {
return err
}
pushOptions.Username = creds.Username
pushOptions.Password = creds.Password
}
// Let's do all the remaining Yoga in the API to prevent us from scattering
// logic across (too) many parts of the code.
return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptions.ImagePushOptions)

View File

@ -1,17 +1,26 @@
package manifest
import (
"context"
"github.com/containers/common/pkg/auth"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// manifestPushOptsWrapper wraps entities.ManifestPushOptions and prevents leaking
// CLI-only fields into the API types.
type manifestPushOptsWrapper struct {
entities.ManifestPushOptions
TLSVerifyCLI bool // CLI only
CredentialsCLI string
}
var (
manifestPushOpts = entities.ManifestPushOptions{}
manifestPushOpts = manifestPushOptsWrapper{}
pushCmd = &cobra.Command{
Use: "push [flags] SOURCE DESTINATION",
Short: "Push a manifest list or image index to a registry",
@ -33,12 +42,12 @@ func init() {
flags.BoolVar(&manifestPushOpts.All, "all", false, "also push the images in the list")
flags.StringVar(&manifestPushOpts.Authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
flags.StringVar(&manifestPushOpts.CertDir, "cert-dir", "", "use certificates at the specified path to access the registry")
flags.StringVar(&manifestPushOpts.Creds, "creds", "", "use `[username[:password]]` for accessing the registry")
flags.StringVar(&manifestPushOpts.CredentialsCLI, "creds", "", "use `[username[:password]]` for accessing the registry")
flags.StringVar(&manifestPushOpts.DigestFile, "digestfile", "", "after copying the image, write the digest of the resulting digest to the file")
flags.StringVarP(&manifestPushOpts.Format, "format", "f", "", "manifest type (oci or v2s2) to attempt to use when pushing the manifest list (default is manifest type of source)")
flags.BoolVarP(&manifestPushOpts.RemoveSignatures, "remove-signatures", "", false, "don't copy signatures when pushing images")
flags.StringVar(&manifestPushOpts.SignBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`")
flags.BoolVar(&manifestPushOpts.TlsVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry")
flags.BoolVar(&manifestPushOpts.TLSVerifyCLI, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry")
flags.BoolVarP(&manifestPushOpts.Quiet, "quiet", "q", false, "don't output progress information when pushing lists")
if registry.IsRemote() {
_ = flags.MarkHidden("authfile")
@ -59,7 +68,24 @@ func push(cmd *cobra.Command, args []string) error {
if destSpec == "" {
return errors.Errorf(`invalid destination "%s"`, destSpec)
}
if err := registry.ImageEngine().ManifestPush(context.Background(), args, manifestPushOpts); err != nil {
if manifestPushOpts.CredentialsCLI != "" {
creds, err := util.ParseRegistryCreds(manifestPushOpts.CredentialsCLI)
if err != nil {
return err
}
manifestPushOpts.Username = creds.Username
manifestPushOpts.Password = creds.Password
}
// TLS verification in c/image is controlled via a `types.OptionalBool`
// which allows for distinguishing among set-true, set-false, unspecified
// which is important to implement a sane way of dealing with defaults of
// boolean CLI flags.
if cmd.Flags().Changed("tls-verify") {
manifestPushOpts.SkipTLSVerify = types.NewOptionalBool(!manifestPushOpts.TLSVerifyCLI)
}
if err := registry.ImageEngine().ManifestPush(registry.Context(), args, manifestPushOpts.ManifestPushOptions); err != nil {
return errors.Wrapf(err, "error pushing manifest %s to %s", listImageSpec, destSpec)
}
return nil

View File

@ -9,6 +9,7 @@ import (
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -18,7 +19,8 @@ import (
type playKubeOptionsWrapper struct {
entities.PlayKubeOptions
TLSVerifyCLI bool
TLSVerifyCLI bool
CredentialsCLI string
}
var (
@ -49,7 +51,7 @@ func init() {
flags := kubeCmd.Flags()
flags.SetNormalizeFunc(utils.AliasFlags)
flags.StringVar(&kubeOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry")
flags.StringVar(&kubeOptions.CredentialsCLI, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry")
flags.StringVar(&kubeOptions.Network, "network", "", "Connect pod to CNI network(s)")
flags.BoolVarP(&kubeOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images")
if !registry.IsRemote() {
@ -76,6 +78,14 @@ func kube(cmd *cobra.Command, args []string) error {
return errors.Wrapf(err, "error getting authfile %s", kubeOptions.Authfile)
}
}
if kubeOptions.CredentialsCLI != "" {
creds, err := util.ParseRegistryCreds(kubeOptions.CredentialsCLI)
if err != nil {
return err
}
kubeOptions.Username = creds.Username
kubeOptions.Password = creds.Password
}
report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), args[0], kubeOptions.PlayKubeOptions)
if err != nil {

View File

@ -206,6 +206,7 @@ func rootFlags(opts *entities.PodmanConfig, flags *pflag.FlagSet) {
flags.IntVar(&opts.MaxWorks, "max-workers", 0, "The maximum number of workers for parallel operations")
flags.StringVar(&cfg.Engine.Namespace, "namespace", cfg.Engine.Namespace, "Set the libpod namespace, used to create separate views of the containers and pods on the system")
flags.StringVar(&cfg.Engine.StaticDir, "root", "", "Path to the root directory in which data, including images, is stored")
flags.StringVar(&opts.RegistriesConf, "registries-conf", "", "Path to a registries.conf to use for image processing")
flags.StringVar(&opts.Runroot, "runroot", "", "Path to the 'run directory' where all state information is stored")
flags.StringVar(&opts.RuntimePath, "runtime", "", "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc")
// -s is deprecated due to conflict with -s on subcommands
@ -225,6 +226,7 @@ func rootFlags(opts *entities.PodmanConfig, flags *pflag.FlagSet) {
"cpu-profile",
"default-mounts-file",
"max-workers",
"registries-conf",
"trace",
} {
if err := flags.MarkHidden(f); err != nil {

View File

@ -45,6 +45,7 @@ case "$SPECIALMODE" in
bindings)
make
make install PREFIX=/usr ETCDIR=/etc
export PATH=$PATH:`pwd`/hack
cd pkg/bindings/test && ginkgo -trace -noColor -debug -r
;;
none)

2
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/containers/buildah v1.14.9-0.20200523094741-de0f541d9224
github.com/containers/common v0.12.0
github.com/containers/conmon v2.0.16+incompatible
github.com/containers/image/v5 v5.4.4
github.com/containers/image/v5 v5.4.5-0.20200529084758-46b2ee6aebb0
github.com/containers/psgo v1.5.0
github.com/containers/storage v1.20.1
github.com/coreos/go-systemd/v22 v22.0.0

16
go.sum
View File

@ -74,8 +74,9 @@ github.com/containers/common v0.12.0/go.mod h1:PKlahPDnQQYcXuIw5qq8mq6yNuCHBtgAB
github.com/containers/conmon v2.0.16+incompatible h1:QFOlb9Id4WoJ24BelCFWwDSPTquwKMp3L3g2iGmRTq4=
github.com/containers/conmon v2.0.16+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/image/v5 v5.4.3/go.mod h1:pN0tvp3YbDd7BWavK2aE0mvJUqVd2HmhPjekyWSFm0U=
github.com/containers/image/v5 v5.4.4 h1:JSanNn3v/BMd3o0MEvO4R4OKNuoJUSzVGQAI1+0FMXE=
github.com/containers/image/v5 v5.4.4/go.mod h1:g7cxNXitiLi6pEr9/L9n/0wfazRuhDKXU15kV86N8h8=
github.com/containers/image/v5 v5.4.5-0.20200529084758-46b2ee6aebb0 h1:K1ez+qAi9hCMHv/akPF4ddZumQTq/PBGf2Nzc7e+7lI=
github.com/containers/image/v5 v5.4.5-0.20200529084758-46b2ee6aebb0/go.mod h1:XRf/UlTCkDBQONudBhSFXiLCouFKPU/oXwIjWw/tPpo=
github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE=
github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
github.com/containers/ocicrypt v1.0.2 h1:Q0/IPs8ohfbXNxEfyJ2pFVmvJu5BhqJUAmc6ES9NKbo=
@ -253,7 +254,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.10.5 h1:7q6vHIqubShURwQz8cQK6yIe/xC3IF0Vm7TGfqjewrc=
github.com/klauspost/compress v1.10.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/pgzip v1.2.3 h1:Ce2to9wvs/cuJ2b86/CKQoTYr9VHfpanYosZ0UBJqdw=
github.com/klauspost/compress v1.10.6 h1:SP6zavvTG3YjOosWePXFDlExpKIWMTO4SE/Y8MZB2vI=
github.com/klauspost/compress v1.10.6/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/pgzip v1.2.3/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A=
github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
@ -268,6 +270,10 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
@ -319,7 +325,6 @@ github.com/onsi/gomega v1.10.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGco
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
@ -332,7 +337,6 @@ github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59P
github.com/opencontainers/runc v1.0.0-rc9 h1:/k06BMULKF5hidyoZymkoDCzdJzltZpz/UU4LguQVtc=
github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v0.1.2-0.20190618234442-a950415649c7 h1:Dliu5QO+4JYWu/yMshaMU7G3JN2POGpwjJN7gjy10Go=
github.com/opencontainers/runtime-spec v0.1.2-0.20190618234442-a950415649c7/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2 h1:9mv9SC7GWmRWE0J/+oD8w3GsN2KYGKtg6uwLN7hfP5E=
github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
@ -452,8 +456,9 @@ github.com/varlink/go v0.0.0-20190502142041-0f1d566d194b/go.mod h1:YHaw8N660ESgM
github.com/vbatts/tar-split v0.11.1 h1:0Odu65rhcZ3JZaPHxl7tCI3V/C/Q9Zf82UFravl02dE=
github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g=
github.com/vbauerster/mpb/v5 v5.0.3/go.mod h1:h3YxU5CSr8rZP4Q3xZPVB3jJLhWPou63lHEdr9ytH4Y=
github.com/vbauerster/mpb/v5 v5.0.4 h1:w7l/tJfHmtIOKZkU+bhbDZOUxj1kln9jy4DUOp3Tl14=
github.com/vbauerster/mpb/v5 v5.0.4/go.mod h1:fvzasBUyuo35UyuA6sSOlVhpLoNQsp2nBdHw7OiSUU8=
github.com/vbauerster/mpb/v5 v5.2.2 h1:zIICVOm+XD+uV6crpSORaL6I0Q1WqOdvxZTp+r3L9cw=
github.com/vbauerster/mpb/v5 v5.2.2/go.mod h1:W5Fvgw4dm3/0NhqzV8j6EacfuTe5SvnzBRwiXxDR9ww=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
@ -549,6 +554,7 @@ golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -30,6 +30,8 @@ type DockerRegistryOptions struct {
OSChoice string
// If not "", overrides the use of platform.GOARCH when choosing an image or verifying architecture match.
ArchitectureChoice string
// RegistriesConfPath can be used to override the default path of registries.conf.
RegistriesConfPath string
}
// GetSystemContext constructs a new system context from a parent context. the values in the DockerRegistryOptions, and other parameters.

View File

@ -8,6 +8,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/namespaces"
@ -17,6 +18,7 @@ import (
"github.com/containers/storage/pkg/idtools"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// Runtime Creation Options
@ -244,6 +246,22 @@ func WithStaticDir(dir string) RuntimeOption {
}
}
// WithRegistriesConf configures the runtime to always use specified
// registries.conf for image processing.
func WithRegistriesConf(path string) RuntimeOption {
logrus.Debugf("Setting custom registries.conf: %q", path)
return func(rt *Runtime) error {
if _, err := os.Stat(path); err != nil {
return errors.Wrap(err, "error locating specified registries.conf")
}
if rt.imageContext == nil {
rt.imageContext = &types.SystemContext{}
}
rt.imageContext.SystemRegistriesConfPath = path
return nil
}
}
// WithHooksDir sets the directories to look for OCI runtime hook configuration.
func WithHooksDir(hooksDirs ...string) RuntimeOption {
return func(rt *Runtime) error {

View File

@ -339,9 +339,10 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) {
}
// Set up containers/image
runtime.imageContext = &types.SystemContext{
SignaturePolicyPath: runtime.config.Engine.SignaturePolicyPath,
if runtime.imageContext == nil {
runtime.imageContext = &types.SystemContext{}
}
runtime.imageContext.SignaturePolicyPath = runtime.config.Engine.SignaturePolicyPath
// Create the tmpDir
if err := os.MkdirAll(runtime.config.Engine.TmpDir, 0751); err != nil {

View File

@ -15,6 +15,7 @@ import (
image2 "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/auth"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/util"
"github.com/docker/docker/api/types"
@ -251,19 +252,32 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
return
}
/*
fromImage Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed.
repo Repository name given to an image when it is imported. The repo may include a tag. This parameter may only be used when importing an image.
tag Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled.
*/
fromImage := query.FromImage
if len(query.Tag) >= 1 {
fromImage = fmt.Sprintf("%s:%s", fromImage, query.Tag)
}
// TODO
// We are eating the output right now because we haven't talked about how to deal with multiple responses yet
img, err := runtime.ImageRuntime().New(r.Context(), fromImage, "", "", nil, &image2.DockerRegistryOptions{}, image2.SigningOptions{}, nil, util.PullImageMissing)
authConf, authfile, err := auth.GetCredentials(r)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
return
}
defer auth.RemoveAuthfile(authfile)
registryOpts := image2.DockerRegistryOptions{DockerRegistryCreds: authConf}
if sys := runtime.SystemContext(); sys != nil {
registryOpts.DockerCertPath = sys.DockerCertPath
}
img, err := runtime.ImageRuntime().New(r.Context(),
fromImage,
"", // signature policy
authfile,
nil, // writer
&registryOpts,
image2.SigningOptions{},
nil, // label
util.PullImageMissing,
)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return

View File

@ -9,6 +9,7 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/auth"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
@ -48,13 +49,17 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
return
}
// TODO: the X-Registry-Auth header is not checked yet here nor in any other
// endpoint. Pushing does NOT work with authentication at the moment.
dockerRegistryOptions := &image.DockerRegistryOptions{}
authfile := ""
authConf, authfile, err := auth.GetCredentials(r)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
return
}
defer auth.RemoveAuthfile(authfile)
dockerRegistryOptions := &image.DockerRegistryOptions{DockerRegistryCreds: authConf}
if sys := runtime.SystemContext(); sys != nil {
dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
authfile = sys.AuthFilePath
dockerRegistryOptions.RegistriesConfPath = sys.SystemRegistriesConfPath
}
err = newImage.PushImageToHeuristicDestination(

View File

@ -21,6 +21,7 @@ import (
image2 "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/auth"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra/abi"
"github.com/containers/libpod/pkg/errorhandling"
@ -28,6 +29,7 @@ import (
utils2 "github.com/containers/libpod/utils"
"github.com/gorilla/schema"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// Commit
@ -339,7 +341,6 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Reference string `schema:"reference"`
Credentials string `schema:"credentials"`
OverrideOS string `schema:"overrideOS"`
OverrideArch string `schema:"overrideArch"`
TLSVerify bool `schema:"tlsVerify"`
@ -382,20 +383,16 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
return
}
var registryCreds *types.DockerAuthConfig
if len(query.Credentials) != 0 {
creds, err := util.ParseRegistryCreds(query.Credentials)
if err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Wrapf(err, "error parsing credentials %q", query.Credentials))
return
}
registryCreds = creds
authConf, authfile, err := auth.GetCredentials(r)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
return
}
defer auth.RemoveAuthfile(authfile)
// Setup the registry options
dockerRegistryOptions := image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,
DockerRegistryCreds: authConf,
OSChoice: query.OverrideOS,
ArchitectureChoice: query.OverrideArch,
}
@ -403,6 +400,13 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
}
sys := runtime.SystemContext()
if sys == nil {
sys = image.GetSystemContext("", authfile, false)
}
dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
sys.DockerAuthConfig = authConf
// Prepare the images we want to pull
imagesToPull := []string{}
res := []handlers.LibpodImagesPullReport{}
@ -411,8 +415,7 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
if !query.AllTags {
imagesToPull = append(imagesToPull, imageName)
} else {
systemContext := image.GetSystemContext("", "", false)
tags, err := docker.GetRepositoryTags(context.Background(), systemContext, imageRef)
tags, err := docker.GetRepositoryTags(context.Background(), sys, imageRef)
if err != nil {
utils.InternalServerError(w, errors.Wrap(err, "error getting repository tags"))
return
@ -422,12 +425,6 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
}
}
authfile := ""
if sys := runtime.SystemContext(); sys != nil {
dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
authfile = sys.AuthFilePath
}
// Finally pull the images
for _, img := range imagesToPull {
newImage, err := runtime.ImageRuntime().New(
@ -456,7 +453,6 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
query := struct {
Credentials string `schema:"credentials"`
Destination string `schema:"destination"`
TLSVerify bool `schema:"tlsVerify"`
}{
@ -492,26 +488,20 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
return
}
var registryCreds *types.DockerAuthConfig
if len(query.Credentials) != 0 {
creds, err := util.ParseRegistryCreds(query.Credentials)
if err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Wrapf(err, "error parsing credentials %q", query.Credentials))
return
}
registryCreds = creds
authConf, authfile, err := auth.GetCredentials(r)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
return
}
defer auth.RemoveAuthfile(authfile)
logrus.Errorf("AuthConf: %v", authConf)
// TODO: the X-Registry-Auth header is not checked yet here nor in any other
// endpoint. Pushing does NOT work with authentication at the moment.
dockerRegistryOptions := &image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,
DockerRegistryCreds: authConf,
}
authfile := ""
if sys := runtime.SystemContext(); sys != nil {
dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
authfile = sys.AuthFilePath
dockerRegistryOptions.RegistriesConfPath = sys.SystemRegistriesConfPath
}
if _, found := r.URL.Query()["tlsVerify"]; found {
dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)

View File

@ -120,6 +120,10 @@ func ManifestRemove(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
}
func ManifestPush(w http.ResponseWriter, r *http.Request) {
// FIXME: parameters are missing (tlsVerify, format).
// Also, we should use the ABI function to avoid duplicate code.
// Also, support for XRegistryAuth headers are missing.
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {

View File

@ -9,6 +9,7 @@ import (
"github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/auth"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra/abi"
"github.com/gorilla/schema"
@ -47,9 +48,26 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error closing temporary file"))
return
}
authConf, authfile, err := auth.GetCredentials(r)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
return
}
defer auth.RemoveAuthfile(authfile)
var username, password string
if authConf != nil {
username = authConf.Username
password = authConf.Password
}
containerEngine := abi.ContainerEngine{Libpod: runtime}
options := entities.PlayKubeOptions{Network: query.Network, Quiet: true}
options := entities.PlayKubeOptions{
Authfile: authfile,
Username: username,
Password: password,
Network: query.Network,
Quiet: true,
}
if _, found := r.URL.Query()["tlsVerify"]; found {
options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
}

View File

@ -8,6 +8,10 @@ import (
"github.com/gorilla/mux"
)
// TODO
//
// * /images/create is missing the "message" and "platform" parameters
func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// swagger:operation POST /images/create compat createImage
// ---
@ -635,10 +639,6 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// type: string
// description: Allows for pushing the image to a different destintation than the image refers to.
// - in: query
// name: credentials
// description: username:password for the registry.
// type: string
// - in: query
// name: tlsVerify
// description: Require TLS verification.
// type: boolean

View File

@ -139,6 +139,13 @@ type swagImageSummary struct {
Body []entities.ImageSummary
}
// Registries summary
// swagger:response DocsRegistriesList
type swagRegistriesList struct {
// in:body
Body entities.ListRegistriesReport
}
// List Containers
// swagger:response DocsListContainer
type swagListContainers struct {

216
pkg/auth/auth.go Normal file
View File

@ -0,0 +1,216 @@
package auth
import (
"encoding/base64"
"encoding/json"
"io/ioutil"
"net/http"
"os"
"strings"
imageAuth "github.com/containers/image/v5/pkg/docker/config"
"github.com/containers/image/v5/types"
dockerAPITypes "github.com/docker/docker/api/types"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// XRegistryAuthHeader is the key to the encoded registry authentication
// configuration in an http-request header.
const XRegistryAuthHeader = "X-Registry-Auth"
// GetCredentials extracts one or more DockerAuthConfigs from the request's
// header. The header could specify a single-auth config in which case the
// first return value is set. In case of a multi-auth header, the contents are
// stored in a temporary auth file (2nd return value). Note that the auth file
// should be removed after usage.
func GetCredentials(r *http.Request) (*types.DockerAuthConfig, string, error) {
authHeader := r.Header.Get(XRegistryAuthHeader)
if len(authHeader) == 0 {
return nil, "", nil
}
// First look for a multi-auth header (i.e., a map).
authConfigs, err := multiAuthHeader(r)
if err == nil {
authfile, err := authConfigsToAuthFile(authConfigs)
return nil, authfile, err
}
// Fallback to looking for a single-auth header (i.e., one config).
authConfigs, err = singleAuthHeader(r)
if err != nil {
return nil, "", err
}
var conf *types.DockerAuthConfig
for k := range authConfigs {
c := authConfigs[k]
conf = &c
break
}
return conf, "", nil
}
// Header returns a map with the XRegistryAuthHeader set which can
// conveniently be used in the http stack.
func Header(sys *types.SystemContext, authfile, username, password string) (map[string]string, error) {
var content string
var err error
if username != "" {
content, err = encodeSingleAuthConfig(types.DockerAuthConfig{Username: username, Password: password})
if err != nil {
return nil, err
}
} else {
if sys == nil {
sys = &types.SystemContext{}
}
if authfile != "" {
sys.AuthFilePath = authfile
}
authConfigs, err := imageAuth.GetAllCredentials(sys)
if err != nil {
return nil, err
}
content, err = encodeMultiAuthConfigs(authConfigs)
if err != nil {
return nil, err
}
}
header := make(map[string]string)
header[XRegistryAuthHeader] = content
return header, nil
}
// RemoveAuthfile is a convenience function that is meant to be called in a
// deferred statement. If non-empty, it removes the specified authfile and log
// errors. It's meant to reduce boilerplate code at call sites of
// `GetCredentials`.
func RemoveAuthfile(authfile string) {
if authfile == "" {
return
}
if err := os.Remove(authfile); err != nil {
logrus.Errorf("Error removing temporary auth file %q: %v", authfile, err)
}
}
// encodeSingleAuthConfig serializes the auth configuration as a base64 encoded JSON payload.
func encodeSingleAuthConfig(authConfig types.DockerAuthConfig) (string, error) {
conf := imageAuthToDockerAuth(authConfig)
buf, err := json.Marshal(conf)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(buf), nil
}
// encodeMultiAuthConfigs serializes the auth configurations as a base64 encoded JSON payload.
func encodeMultiAuthConfigs(authConfigs map[string]types.DockerAuthConfig) (string, error) {
confs := make(map[string]dockerAPITypes.AuthConfig)
for registry, authConf := range authConfigs {
confs[registry] = imageAuthToDockerAuth(authConf)
}
buf, err := json.Marshal(confs)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(buf), nil
}
// authConfigsToAuthFile stores the specified auth configs in a temporary files
// and returns its path. The file can later be used an auth file for contacting
// one or more container registries. If tmpDir is empty, the system's default
// TMPDIR will be used.
func authConfigsToAuthFile(authConfigs map[string]types.DockerAuthConfig) (string, error) {
// Intitialize an empty temporary JSON file.
tmpFile, err := ioutil.TempFile("", "auth.json.")
if err != nil {
return "", err
}
if _, err := tmpFile.Write([]byte{'{', '}'}); err != nil {
return "", errors.Wrap(err, "error initializing temporary auth file")
}
if err := tmpFile.Close(); err != nil {
return "", errors.Wrap(err, "error closing temporary auth file")
}
authFilePath := tmpFile.Name()
// TODO: It would be nice if c/image could dump the map at once.
//
// Now use the c/image packages to store the credentials. It's battle
// tested, and we make sure to use the same code as the image backend.
sys := types.SystemContext{AuthFilePath: authFilePath}
for server, config := range authConfigs {
// Note that we do not validate the credentials here. Wassume
// that all credentials are valid. They'll be used on demand
// later.
if err := imageAuth.SetAuthentication(&sys, server, config.Username, config.Password); err != nil {
return "", errors.Wrapf(err, "error storing credentials in temporary auth file (server: %q, user: %q)", server, config.Username)
}
}
return authFilePath, nil
}
// dockerAuthToImageAuth converts a docker auth config to one we're using
// internally from c/image. Note that the Docker types look slightly
// different, so we need to convert to be extra sure we're not running into
// undesired side-effects when unmarhalling directly to our types.
func dockerAuthToImageAuth(authConfig dockerAPITypes.AuthConfig) types.DockerAuthConfig {
return types.DockerAuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
IdentityToken: authConfig.IdentityToken,
}
}
// reverse conversion of `dockerAuthToImageAuth`.
func imageAuthToDockerAuth(authConfig types.DockerAuthConfig) dockerAPITypes.AuthConfig {
return dockerAPITypes.AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
IdentityToken: authConfig.IdentityToken,
}
}
// singleAuthHeader extracts a DockerAuthConfig from the request's header.
// The header content is a single DockerAuthConfig.
func singleAuthHeader(r *http.Request) (map[string]types.DockerAuthConfig, error) {
authHeader := r.Header.Get(XRegistryAuthHeader)
authConfig := dockerAPITypes.AuthConfig{}
if len(authHeader) > 0 {
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader))
if err := json.NewDecoder(authJSON).Decode(&authConfig); err != nil {
return nil, err
}
}
authConfigs := make(map[string]types.DockerAuthConfig)
authConfigs["0"] = dockerAuthToImageAuth(authConfig)
return authConfigs, nil
}
// multiAuthHeader extracts a DockerAuthConfig from the request's header.
// The header content is a map[string]DockerAuthConfigs.
func multiAuthHeader(r *http.Request) (map[string]types.DockerAuthConfig, error) {
authHeader := r.Header.Get(XRegistryAuthHeader)
if len(authHeader) == 0 {
return nil, nil
}
dockerAuthConfigs := make(map[string]dockerAPITypes.AuthConfig)
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader))
if err := json.NewDecoder(authJSON).Decode(&dockerAuthConfigs); err != nil {
return nil, err
}
// Now convert to the internal types.
authConfigs := make(map[string]types.DockerAuthConfig)
for server := range dockerAuthConfigs {
authConfigs[server] = dockerAuthToImageAuth(dockerAuthConfigs[server])
}
return authConfigs, nil
}

View File

@ -150,7 +150,7 @@ func pingNewConnection(ctx context.Context) error {
return err
}
// the ping endpoint sits at / in this case
response, err := client.DoRequest(nil, http.MethodGet, "../../../_ping", nil)
response, err := client.DoRequest(nil, http.MethodGet, "../../../_ping", nil, nil)
if err != nil {
return err
}
@ -250,7 +250,7 @@ func unixClient(_url *url.URL) (Connection, error) {
}
// DoRequest assembles the http request and returns the response
func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, pathValues ...string) (*APIResponse, error) {
func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, header map[string]string, pathValues ...string) (*APIResponse, error) {
var (
err error
response *http.Response
@ -271,6 +271,9 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string,
if len(queryParams) > 0 {
req.URL.RawQuery = queryParams.Encode()
}
for key, val := range header {
req.Header.Set(key, val)
}
req = req.WithContext(context.WithValue(context.Background(), clientKey, c))
// Give the Do three chances in the case of a comm/service hiccup
for i := 0; i < 3; i++ {

View File

@ -34,7 +34,7 @@ func Checkpoint(ctx context.Context, nameOrId string, keep, leaveRunning, tcpEst
if export != nil {
params.Set("export", *export)
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/checkpoint", params, nameOrId)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/checkpoint", params, nil, nameOrId)
if err != nil {
return nil, err
}
@ -71,7 +71,7 @@ func Restore(ctx context.Context, nameOrId string, keep, tcpEstablished, ignoreR
if importArchive != nil {
params.Set("import", *importArchive)
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restore", params, nameOrId)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restore", params, nil, nameOrId)
if err != nil {
return nil, err
}

View File

@ -41,7 +41,7 @@ func Commit(ctx context.Context, nameOrId string, options CommitOptions) (handle
if options.Tag != nil {
params.Set("tag", *options.Tag)
}
response, err := conn.DoRequest(nil, http.MethodPost, "/commit", params)
response, err := conn.DoRequest(nil, http.MethodPost, "/commit", params, nil)
if err != nil {
return id, err
}

View File

@ -61,7 +61,7 @@ func List(ctx context.Context, filters map[string][]string, all *bool, last *int
}
params.Set("filters", filterString)
}
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/json", params)
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/json", params, nil)
if err != nil {
return containers, err
}
@ -86,7 +86,7 @@ func Prune(ctx context.Context, filters map[string][]string) (*entities.Containe
}
params.Set("filters", filterString)
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params, nil)
if err != nil {
return nil, err
}
@ -108,7 +108,7 @@ func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error {
if volumes != nil {
params.Set("vols", strconv.FormatBool(*volumes))
}
response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nil, nameOrID)
if err != nil {
return err
}
@ -128,7 +128,7 @@ func Inspect(ctx context.Context, nameOrID string, size *bool) (*define.InspectC
if size != nil {
params.Set("size", strconv.FormatBool(*size))
}
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/json", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/json", params, nil, nameOrID)
if err != nil {
return nil, err
}
@ -146,7 +146,7 @@ func Kill(ctx context.Context, nameOrID string, sig string) error {
}
params := url.Values{}
params.Set("signal", sig)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/kill", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/kill", params, nil, nameOrID)
if err != nil {
return err
}
@ -161,7 +161,7 @@ func Pause(ctx context.Context, nameOrID string) error {
if err != nil {
return err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/pause", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/pause", nil, nil, nameOrID)
if err != nil {
return err
}
@ -180,7 +180,7 @@ func Restart(ctx context.Context, nameOrID string, timeout *int) error {
if timeout != nil {
params.Set("t", strconv.Itoa(*timeout))
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restart", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restart", params, nil, nameOrID)
if err != nil {
return err
}
@ -199,7 +199,7 @@ func Start(ctx context.Context, nameOrID string, detachKeys *string) error {
if detachKeys != nil {
params.Set("detachKeys", *detachKeys)
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/start", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/start", params, nil, nameOrID)
if err != nil {
return err
}
@ -221,7 +221,7 @@ func Top(ctx context.Context, nameOrID string, descriptors []string) ([]string,
// flatten the slice into one string
params.Set("ps_args", strings.Join(descriptors, ","))
}
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/top", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/top", params, nil, nameOrID)
if err != nil {
return nil, err
}
@ -249,7 +249,7 @@ func Unpause(ctx context.Context, nameOrID string) error {
if err != nil {
return err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unpause", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unpause", nil, nil, nameOrID)
if err != nil {
return err
}
@ -269,7 +269,7 @@ func Wait(ctx context.Context, nameOrID string, condition *define.ContainerStatu
if condition != nil {
params.Set("condition", condition.String())
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/wait", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/wait", params, nil, nameOrID)
if err != nil {
return exitCode, err
}
@ -284,7 +284,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) {
if err != nil {
return false, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/exists", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/exists", nil, nil, nameOrID)
if err != nil {
return false, err
}
@ -302,7 +302,7 @@ func Stop(ctx context.Context, nameOrID string, timeout *uint) error {
if timeout != nil {
params.Set("t", strconv.Itoa(int(*timeout)))
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/stop", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/stop", params, nil, nameOrID)
if err != nil {
return err
}
@ -317,7 +317,7 @@ func Export(ctx context.Context, nameOrID string, w io.Writer) error {
if err != nil {
return err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/export", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/export", params, nil, nameOrID)
if err != nil {
return err
}
@ -336,7 +336,7 @@ func ContainerInit(ctx context.Context, nameOrID string) error {
if err != nil {
return err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/init", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/init", nil, nil, nameOrID)
if err != nil {
return err
}
@ -443,7 +443,7 @@ func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stre
}()
}
response, err := conn.DoRequest(stdin, http.MethodPost, "/containers/%s/attach", params, nameOrId)
response, err := conn.DoRequest(stdin, http.MethodPost, "/containers/%s/attach", params, nil, nameOrId)
if err != nil {
return err
}
@ -572,7 +572,7 @@ func resizeTTY(ctx context.Context, endpoint string, height *int, width *int) er
if width != nil {
params.Set("w", strconv.Itoa(*width))
}
rsp, err := conn.DoRequest(nil, http.MethodPost, endpoint, params)
rsp, err := conn.DoRequest(nil, http.MethodPost, endpoint, params, nil)
if err != nil {
return err
}

View File

@ -22,7 +22,7 @@ func CreateWithSpec(ctx context.Context, s *specgen.SpecGenerator) (entities.Con
return ccr, err
}
stringReader := strings.NewReader(specgenString)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil, nil)
if err != nil {
return ccr, err
}

View File

@ -15,7 +15,7 @@ func Diff(ctx context.Context, nameOrId string) ([]archive.Change, error) {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/changes", nil, nameOrId)
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/changes", nil, nil, nameOrId)
if err != nil {
return nil, err
}

View File

@ -34,7 +34,7 @@ func ExecCreate(ctx context.Context, nameOrID string, config *handlers.ExecCreat
}
jsonReader := strings.NewReader(string(requestJSON))
resp, err := conn.DoRequest(jsonReader, http.MethodPost, "/containers/%s/exec", nil, nameOrID)
resp, err := conn.DoRequest(jsonReader, http.MethodPost, "/containers/%s/exec", nil, nil, nameOrID)
if err != nil {
return "", err
}
@ -57,7 +57,7 @@ func ExecInspect(ctx context.Context, sessionID string) (*define.InspectExecSess
logrus.Debugf("Inspecting session ID %q", sessionID)
resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, sessionID)
resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, nil, sessionID)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,7 @@ func RunHealthCheck(ctx context.Context, nameOrID string) (*define.HealthCheckRe
var (
status define.HealthCheckResults
)
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/healthcheck", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/healthcheck", nil, nil, nameOrID)
if err != nil {
return nil, err
}

View File

@ -46,7 +46,7 @@ func Logs(ctx context.Context, nameOrID string, opts LogOptions, stdoutChan, std
if opts.Stdout == nil && opts.Stderr == nil {
params.Set("stdout", strconv.FormatBool(true))
}
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/logs", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/logs", params, nil, nameOrID)
if err != nil {
return err
}

View File

@ -17,7 +17,7 @@ func Mount(ctx context.Context, nameOrID string) (string, error) {
var (
path string
)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/mount", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/mount", nil, nil, nameOrID)
if err != nil {
return path, err
}
@ -31,7 +31,7 @@ func Unmount(ctx context.Context, nameOrID string) error {
if err != nil {
return err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unmount", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unmount", nil, nil, nameOrID)
if err != nil {
return err
}
@ -45,7 +45,7 @@ func GetMountedContainerPaths(ctx context.Context) (map[string]string, error) {
return nil, err
}
mounts := make(map[string]string)
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/showmounted", nil)
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/showmounted", nil, nil)
if err != nil {
return mounts, err
}

View File

@ -18,7 +18,7 @@ func GenerateKube(ctx context.Context, nameOrID string, options entities.Generat
params := url.Values{}
params.Set("service", strconv.FormatBool(options.Service))
response, err := conn.DoRequest(nil, http.MethodGet, "/generate/%s/kube", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/generate/%s/kube", params, nil, nameOrID)
if err != nil {
return nil, err
}

View File

@ -15,7 +15,7 @@ func Diff(ctx context.Context, nameOrId string) ([]archive.Change, error) {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/changes", nil, nameOrId)
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/changes", nil, nil, nameOrId)
if err != nil {
return nil, err
}

View File

@ -12,6 +12,7 @@ import (
"github.com/containers/buildah"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/auth"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/docker/go-units"
@ -26,7 +27,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) {
if err != nil {
return false, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/exists", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/exists", nil, nil, nameOrID)
if err != nil {
return false, err
}
@ -52,7 +53,7 @@ func List(ctx context.Context, all *bool, filters map[string][]string) ([]*entit
}
params.Set("filters", strFilters)
}
response, err := conn.DoRequest(nil, http.MethodGet, "/images/json", params)
response, err := conn.DoRequest(nil, http.MethodGet, "/images/json", params, nil)
if err != nil {
return imageSummary, err
}
@ -71,7 +72,7 @@ func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.Image
params.Set("size", strconv.FormatBool(*size))
}
inspectedData := entities.ImageInspectReport{}
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nil, nameOrID)
if err != nil {
return &inspectedData, err
}
@ -89,7 +90,7 @@ func Tree(ctx context.Context, nameOrId string, whatRequires *bool) (*entities.I
if whatRequires != nil {
params.Set("size", strconv.FormatBool(*whatRequires))
}
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/tree", params, nameOrId)
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/tree", params, nil, nameOrId)
if err != nil {
return nil, err
}
@ -103,7 +104,7 @@ func History(ctx context.Context, nameOrID string) ([]*handlers.HistoryResponse,
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/history", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/history", nil, nil, nameOrID)
if err != nil {
return history, err
}
@ -120,7 +121,7 @@ func Load(ctx context.Context, r io.Reader, name *string) (*entities.ImageLoadRe
if name != nil {
params.Set("reference", *name)
}
response, err := conn.DoRequest(r, http.MethodPost, "/images/load", params)
response, err := conn.DoRequest(r, http.MethodPost, "/images/load", params, nil)
if err != nil {
return nil, err
}
@ -141,7 +142,7 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, c
if compress != nil {
params.Set("compress", strconv.FormatBool(*compress))
}
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/get", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/get", params, nil, nameOrID)
if err != nil {
return err
}
@ -174,7 +175,7 @@ func Prune(ctx context.Context, all *bool, filters map[string][]string) ([]strin
}
params.Set("filters", stringFilter)
}
response, err := conn.DoRequest(nil, http.MethodPost, "/images/prune", params)
response, err := conn.DoRequest(nil, http.MethodPost, "/images/prune", params, nil)
if err != nil {
return deleted, err
}
@ -190,7 +191,7 @@ func Tag(ctx context.Context, nameOrID, tag, repo string) error {
params := url.Values{}
params.Set("tag", tag)
params.Set("repo", repo)
response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/tag", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/tag", params, nil, nameOrID)
if err != nil {
return err
}
@ -206,7 +207,7 @@ func Untag(ctx context.Context, nameOrID, tag, repo string) error {
params := url.Values{}
params.Set("tag", tag)
params.Set("repo", repo)
response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/untag", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/untag", params, nil, nameOrID)
if err != nil {
return err
}
@ -297,7 +298,7 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
}
// TODO outputs?
response, err := conn.DoRequest(tarfile, http.MethodPost, "/build", params)
response, err := conn.DoRequest(tarfile, http.MethodPost, "/build", params, nil)
if err != nil {
return nil, err
}
@ -341,7 +342,7 @@ func Import(ctx context.Context, changes []string, message, reference, u *string
if u != nil {
params.Set("url", *u)
}
response, err := conn.DoRequest(r, http.MethodPost, "/images/import", params)
response, err := conn.DoRequest(r, http.MethodPost, "/images/import", params, nil)
if err != nil {
return nil, err
}
@ -359,7 +360,6 @@ func Pull(ctx context.Context, rawImage string, options entities.ImagePullOption
}
params := url.Values{}
params.Set("reference", rawImage)
params.Set("credentials", options.Credentials)
params.Set("overrideArch", options.OverrideArch)
params.Set("overrideOS", options.OverrideOS)
if options.SkipTLSVerify != types.OptionalBoolUndefined {
@ -369,7 +369,13 @@ func Pull(ctx context.Context, rawImage string, options entities.ImagePullOption
}
params.Set("allTags", strconv.FormatBool(options.AllTags))
response, err := conn.DoRequest(nil, http.MethodPost, "/images/pull", params)
// TODO: have a global system context we can pass around (1st argument)
header, err := auth.Header(nil, options.Authfile, options.Username, options.Password)
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/images/pull", params, header)
if err != nil {
return nil, err
}
@ -397,8 +403,14 @@ func Push(ctx context.Context, source string, destination string, options entiti
if err != nil {
return err
}
// TODO: have a global system context we can pass around (1st argument)
header, err := auth.Header(nil, options.Authfile, options.Username, options.Password)
if err != nil {
return err
}
params := url.Values{}
params.Set("credentials", options.Credentials)
params.Set("destination", destination)
if options.SkipTLSVerify != types.OptionalBoolUndefined {
// Note: we have to verify if skipped is false.
@ -407,8 +419,12 @@ func Push(ctx context.Context, source string, destination string, options entiti
}
path := fmt.Sprintf("/images/%s/push", source)
_, err = conn.DoRequest(nil, http.MethodPost, path, params)
return err
response, err := conn.DoRequest(nil, http.MethodPost, path, params, header)
if err != nil {
return err
}
return response.Process(err)
}
// Search is the binding for libpod's v2 endpoints for Search images.
@ -430,7 +446,13 @@ func Search(ctx context.Context, term string, opts entities.ImageSearchOptions)
params.Set("tlsVerify", strconv.FormatBool(verifyTLS))
}
response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params)
// TODO: have a global system context we can pass around (1st argument)
header, err := auth.Header(nil, opts.Authfile, "", "")
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params, header)
if err != nil {
return nil, err
}

View File

@ -30,7 +30,7 @@ func BatchRemove(ctx context.Context, images []string, opts entities.ImageRemove
params.Add("images", i)
}
response, err := conn.DoRequest(nil, http.MethodDelete, "/images/remove", params)
response, err := conn.DoRequest(nil, http.MethodDelete, "/images/remove", params, nil)
if err != nil {
return nil, []error{err}
}
@ -52,7 +52,7 @@ func Remove(ctx context.Context, nameOrID string, force bool) (*entities.ImageRe
params := url.Values{}
params.Set("force", strconv.FormatBool(force))
response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nil, nameOrID)
if err != nil {
return nil, err
}

View File

@ -39,7 +39,7 @@ func Create(ctx context.Context, names, images []string, all *bool) (string, err
params.Add("image", i)
}
response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/create", params)
response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/create", params, nil)
if err != nil {
return "", err
}
@ -53,7 +53,7 @@ func Inspect(ctx context.Context, name string) (*manifest.Schema2List, error) {
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/json", nil, name)
response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/json", nil, nil, name)
if err != nil {
return nil, err
}
@ -73,7 +73,7 @@ func Add(ctx context.Context, name string, options image.ManifestAddOpts) (strin
return "", err
}
stringReader := strings.NewReader(optionsString)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/add", nil, name)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/add", nil, nil, name)
if err != nil {
return "", err
}
@ -90,7 +90,7 @@ func Remove(ctx context.Context, name, digest string) (string, error) {
}
params := url.Values{}
params.Set("digest", digest)
response, err := conn.DoRequest(nil, http.MethodDelete, "/manifests/%s", params, name)
response, err := conn.DoRequest(nil, http.MethodDelete, "/manifests/%s", params, nil, name)
if err != nil {
return "", err
}
@ -118,7 +118,7 @@ func Push(ctx context.Context, name string, destination *string, all *bool) (str
if all != nil {
params.Set("all", strconv.FormatBool(*all))
}
_, err = conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, name)
_, err = conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, nil, name)
if err != nil {
return "", err
}

View File

@ -28,7 +28,7 @@ func Create(ctx context.Context, options entities.NetworkCreateOptions, name *st
return nil, err
}
stringReader := strings.NewReader(networkConfig)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/networks/create", params)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/networks/create", params, nil)
if err != nil {
return nil, err
}
@ -42,7 +42,7 @@ func Inspect(ctx context.Context, nameOrID string) ([]entities.NetworkInspectRep
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/networks/%s/json", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/networks/%s/json", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@ -62,7 +62,7 @@ func Remove(ctx context.Context, nameOrID string, force *bool) ([]*entities.Netw
if force != nil {
params.Set("size", strconv.FormatBool(*force))
}
response, err := conn.DoRequest(nil, http.MethodDelete, "/networks/%s", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodDelete, "/networks/%s", params, nil, nameOrID)
if err != nil {
return nil, err
}
@ -78,7 +78,7 @@ func List(ctx context.Context) ([]*entities.NetworkListReport, error) {
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", nil)
response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", nil, nil)
if err != nil {
return netList, err
}

View File

@ -8,6 +8,7 @@ import (
"strconv"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/pkg/auth"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/domain/entities"
)
@ -31,7 +32,13 @@ func PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions
params.Set("tlsVerify", strconv.FormatBool(options.SkipTLSVerify == types.OptionalBoolTrue))
}
response, err := conn.DoRequest(f, http.MethodPost, "/play/kube", params)
// TODO: have a global system context we can pass around (1st argument)
header, err := auth.Header(nil, options.Authfile, options.Username, options.Password)
if err != nil {
return nil, err
}
response, err := conn.DoRequest(f, http.MethodPost, "/play/kube", params, header)
if err != nil {
return nil, err
}

View File

@ -28,7 +28,7 @@ func CreatePodFromSpec(ctx context.Context, s *specgen.PodSpecGenerator) (*entit
return nil, err
}
stringReader := strings.NewReader(specgenString)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil, nil)
if err != nil {
return nil, err
}
@ -41,7 +41,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) {
if err != nil {
return false, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/exists", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/exists", nil, nil, nameOrID)
if err != nil {
return false, err
}
@ -57,7 +57,7 @@ func Inspect(ctx context.Context, nameOrID string) (*entities.PodInspectReport,
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/json", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/json", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@ -78,7 +78,7 @@ func Kill(ctx context.Context, nameOrID string, signal *string) (*entities.PodKi
if signal != nil {
params.Set("signal", *signal)
}
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nil, nameOrID)
if err != nil {
return nil, err
}
@ -92,7 +92,7 @@ func Pause(ctx context.Context, nameOrID string) (*entities.PodPauseReport, erro
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/pause", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/pause", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@ -107,7 +107,7 @@ func Prune(ctx context.Context) ([]*entities.PodPruneReport, error) {
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil)
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil, nil)
if err != nil {
return nil, err
}
@ -132,7 +132,7 @@ func List(ctx context.Context, filters map[string][]string) ([]*entities.ListPod
}
params.Set("filters", stringFilter)
}
response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params)
response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params, nil)
if err != nil {
return podsReports, err
}
@ -146,7 +146,7 @@ func Restart(ctx context.Context, nameOrID string) (*entities.PodRestartReport,
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/restart", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/restart", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@ -165,7 +165,7 @@ func Remove(ctx context.Context, nameOrID string, force *bool) (*entities.PodRmR
if force != nil {
params.Set("force", strconv.FormatBool(*force))
}
response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nil, nameOrID)
if err != nil {
return nil, err
}
@ -179,7 +179,7 @@ func Start(ctx context.Context, nameOrID string) (*entities.PodStartReport, erro
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/start", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/start", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@ -202,7 +202,7 @@ func Stop(ctx context.Context, nameOrID string, timeout *int) (*entities.PodStop
if timeout != nil {
params.Set("t", strconv.Itoa(*timeout))
}
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nil, nameOrID)
if err != nil {
return nil, err
}
@ -226,7 +226,7 @@ func Top(ctx context.Context, nameOrID string, descriptors []string) ([]string,
// flatten the slice into one string
params.Set("ps_args", strings.Join(descriptors, ","))
}
response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/top", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/top", params, nil, nameOrID)
if err != nil {
return nil, err
}
@ -254,7 +254,7 @@ func Unpause(ctx context.Context, nameOrID string) (*entities.PodUnpauseReport,
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/unpause", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/unpause", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@ -277,7 +277,7 @@ func Stats(ctx context.Context, namesOrIDs []string, options entities.PodStatsOp
params.Set("all", strconv.FormatBool(options.All))
var reports []*entities.PodStatsReport
response, err := conn.DoRequest(nil, http.MethodGet, "/pods/stats", params)
response, err := conn.DoRequest(nil, http.MethodGet, "/pods/stats", params, nil)
if err != nil {
return nil, err
}

View File

@ -15,7 +15,7 @@ func Info(ctx context.Context) (*define.Info, error) {
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/info", nil)
response, err := conn.DoRequest(nil, http.MethodGet, "/info", nil, nil)
if err != nil {
return nil, err
}

View File

@ -42,7 +42,7 @@ func Events(ctx context.Context, eventChan chan entities.Event, cancelChan chan
}
params.Set("filters", filterString)
}
response, err := conn.DoRequest(nil, http.MethodGet, "/events", params)
response, err := conn.DoRequest(nil, http.MethodGet, "/events", params, nil)
if err != nil {
return err
}
@ -89,7 +89,7 @@ func Prune(ctx context.Context, all, volumes *bool) (*entities.SystemPruneReport
if volumes != nil {
params.Set("Volumes", strconv.FormatBool(*volumes))
}
response, err := conn.DoRequest(nil, http.MethodPost, "/system/prune", params)
response, err := conn.DoRequest(nil, http.MethodPost, "/system/prune", params, nil)
if err != nil {
return nil, err
}
@ -110,7 +110,7 @@ func Version(ctx context.Context) (*entities.SystemVersionReport, error) {
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/version", nil)
response, err := conn.DoRequest(nil, http.MethodGet, "/version", nil, nil)
if err != nil {
return nil, err
}
@ -139,7 +139,7 @@ func DiskUsage(ctx context.Context) (*entities.SystemDfReport, error) {
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/system/df", nil)
response, err := conn.DoRequest(nil, http.MethodGet, "/system/df", nil, nil)
if err != nil {
return nil, err
}

View File

@ -0,0 +1,143 @@
package test_bindings
import (
"io/ioutil"
"os"
"time"
"github.com/containers/common/pkg/auth"
"github.com/containers/image/v5/types"
podmanRegistry "github.com/containers/libpod/hack/podman-registry-go"
"github.com/containers/libpod/pkg/bindings/images"
"github.com/containers/libpod/pkg/domain/entities"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman images", func() {
var (
registry *podmanRegistry.Registry
bt *bindingTest
s *gexec.Session
err error
)
BeforeEach(func() {
// Note: we need to start the registry **before** setting up
// the test. Otherwise, the registry is not reachable for
// currently unknown reasons.
registry, err = podmanRegistry.Start()
Expect(err).To(BeNil())
bt = newBindingTest()
bt.RestoreImagesFromCache()
s = bt.startAPIService()
time.Sleep(1 * time.Second)
err := bt.NewConnection()
Expect(err).To(BeNil())
})
AfterEach(func() {
s.Kill()
bt.cleanup()
registry.Stop()
})
// Test using credentials.
It("tag + push + pull (with credentials)", func() {
imageRep := "localhost:" + registry.Port + "/test"
imageTag := "latest"
imageRef := imageRep + ":" + imageTag
// Tag the alpine image and verify it has worked.
err = images.Tag(bt.conn, alpine.shortName, imageTag, imageRep)
Expect(err).To(BeNil())
_, err = images.GetImage(bt.conn, imageRef, nil)
Expect(err).To(BeNil())
// Now push the image.
pushOpts := entities.ImagePushOptions{
Username: registry.User,
Password: registry.Password,
SkipTLSVerify: types.OptionalBoolTrue,
}
err = images.Push(bt.conn, imageRef, imageRef, pushOpts)
Expect(err).To(BeNil())
// Now pull the image.
pullOpts := entities.ImagePullOptions{
Username: registry.User,
Password: registry.Password,
SkipTLSVerify: types.OptionalBoolTrue,
}
_, err = images.Pull(bt.conn, imageRef, pullOpts)
Expect(err).To(BeNil())
})
// Test using authfile.
It("tag + push + pull + search (with authfile)", func() {
imageRep := "localhost:" + registry.Port + "/test"
imageTag := "latest"
imageRef := imageRep + ":" + imageTag
// Create a temporary authentication file.
tmpFile, err := ioutil.TempFile("", "auth.json.")
Expect(err).To(BeNil())
_, err = tmpFile.Write([]byte{'{', '}'})
Expect(err).To(BeNil())
err = tmpFile.Close()
Expect(err).To(BeNil())
authFilePath := tmpFile.Name()
// Now login to a) test the credentials and to b) store them in
// the authfile for later use.
sys := types.SystemContext{
AuthFilePath: authFilePath,
DockerInsecureSkipTLSVerify: types.OptionalBoolTrue,
}
loginOptions := auth.LoginOptions{
Username: registry.User,
Password: registry.Password,
AuthFile: authFilePath,
Stdin: os.Stdin,
Stdout: os.Stdout,
}
err = auth.Login(bt.conn, &sys, &loginOptions, []string{imageRep})
Expect(err).To(BeNil())
// Tag the alpine image and verify it has worked.
err = images.Tag(bt.conn, alpine.shortName, imageTag, imageRep)
Expect(err).To(BeNil())
_, err = images.GetImage(bt.conn, imageRef, nil)
Expect(err).To(BeNil())
// Now push the image.
pushOpts := entities.ImagePushOptions{
Authfile: authFilePath,
SkipTLSVerify: types.OptionalBoolTrue,
}
err = images.Push(bt.conn, imageRef, imageRef, pushOpts)
Expect(err).To(BeNil())
// Now pull the image.
pullOpts := entities.ImagePullOptions{
Authfile: authFilePath,
SkipTLSVerify: types.OptionalBoolTrue,
}
_, err = images.Pull(bt.conn, imageRef, pullOpts)
Expect(err).To(BeNil())
// Last, but not least, exercise search.
searchOptions := entities.ImageSearchOptions{
Authfile: authFilePath,
SkipTLSVerify: types.OptionalBoolTrue,
}
_, err = images.Search(bt.conn, imageRef, searchOptions)
Expect(err).To(BeNil())
})
})

View File

@ -26,7 +26,7 @@ func Create(ctx context.Context, config entities.VolumeCreateOptions) (*entities
return nil, err
}
stringReader := strings.NewReader(createString)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/volumes/create", nil)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/volumes/create", nil, nil)
if err != nil {
return nil, err
}
@ -42,7 +42,7 @@ func Inspect(ctx context.Context, nameOrID string) (*entities.VolumeConfigRespon
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/%s/json", nil, nameOrID)
response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/%s/json", nil, nil, nameOrID)
if err != nil {
return &inspect, err
}
@ -67,7 +67,7 @@ func List(ctx context.Context, filters map[string][]string) ([]*entities.VolumeL
}
params.Set("filters", strFilters)
}
response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/json", params)
response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/json", params, nil)
if err != nil {
return vols, err
}
@ -83,7 +83,7 @@ func Prune(ctx context.Context) ([]*entities.VolumePruneReport, error) {
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil)
response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil, nil)
if err != nil {
return nil, err
}
@ -101,7 +101,7 @@ func Remove(ctx context.Context, nameOrID string, force *bool) error {
if force != nil {
params.Set("force", strconv.FormatBool(*force))
}
response, err := conn.DoRequest(nil, http.MethodDelete, "/volumes/%s", params, nameOrID)
response, err := conn.DoRequest(nil, http.MethodDelete, "/volumes/%s", params, nil, nameOrID)
if err != nil {
return err
}

View File

@ -37,19 +37,20 @@ type PodmanConfig struct {
*config.Config
*pflag.FlagSet
CGroupUsage string // rootless code determines Usage message
ConmonPath string // --conmon flag will set Engine.ConmonPath
CpuProfile string // Hidden: Should CPU profile be taken
EngineMode EngineMode // ABI or Tunneling mode
Identities []string // ssh identities for connecting to server
MaxWorks int // maximum number of parallel threads
RuntimePath string // --runtime flag will set Engine.RuntimePath
SpanCloser io.Closer // Close() for tracing object
SpanCtx context.Context // context to use when tracing
Span opentracing.Span // tracing object
Syslog bool // write to StdOut and Syslog, not supported when tunneling
Trace bool // Hidden: Trace execution
Uri string // URI to API Service
CGroupUsage string // rootless code determines Usage message
ConmonPath string // --conmon flag will set Engine.ConmonPath
CpuProfile string // Hidden: Should CPU profile be taken
EngineMode EngineMode // ABI or Tunneling mode
Identities []string // ssh identities for connecting to server
MaxWorks int // maximum number of parallel threads
RegistriesConf string // allows for specifying a custom registries.conf
RuntimePath string // --runtime flag will set Engine.RuntimePath
SpanCloser io.Closer // Close() for tracing object
SpanCtx context.Context // context to use when tracing
Span opentracing.Span // tracing object
Syslog bool // write to StdOut and Syslog, not supported when tunneling
Trace bool // Hidden: Trace execution
Uri string // URI to API Service
Runroot string
StorageDriver string

View File

@ -128,9 +128,10 @@ type ImagePullOptions struct {
// CertDir is the path to certificate directories. Ignored for remote
// calls.
CertDir string
// Credentials for authenticating against the registry in the format
// USERNAME:PASSWORD.
Credentials string
// Username for authenticating against the registry.
Username string
// Password for authenticating against the registry.
Password string
// OverrideArch will overwrite the local architecture for image pulls.
OverrideArch string
// OverrideOS will overwrite the local operating system (OS) for image
@ -162,9 +163,10 @@ type ImagePushOptions struct {
// transport. Default is same compression type as source. Ignored for remote
// calls.
Compress bool
// Credentials for authenticating against the registry in the format
// USERNAME:PASSWORD.
Credentials string
// Username for authenticating against the registry.
Username string
// Password for authenticating against the registry.
Password string
// DigestFile, after copying the image, write the digest of the resulting
// image to the file. Ignored for remote calls.
DigestFile string

View File

@ -1,5 +1,9 @@
package entities
import "github.com/containers/image/v5/types"
// TODO: add comments to *all* types and fields.
type ManifestCreateOptions struct {
All bool `schema:"all"`
}
@ -26,6 +30,9 @@ type ManifestAnnotateOptions struct {
}
type ManifestPushOptions struct {
Purge, Quiet, All, TlsVerify, RemoveSignatures bool
Authfile, CertDir, Creds, DigestFile, Format, SignBy string
Purge, Quiet, All, RemoveSignatures bool
Authfile, CertDir, Username, Password, DigestFile, Format, SignBy string
SkipTLSVerify types.OptionalBool
}

View File

@ -8,9 +8,10 @@ type PlayKubeOptions struct {
Authfile string
// CertDir - to a directory containing TLS certifications and keys.
CertDir string
// Credentials - `username:password` for authentication against a
// container registry.
Credentials string
// Username for authenticating against the registry.
Username string
// Password for authenticating against the registry.
Password string
// Network - name of the CNI network to connect to.
Network string
// Quiet - suppress output when pulling images.

View File

@ -97,3 +97,9 @@ type SystemVersionReport struct {
type ComponentVersion struct {
types.Version
}
// ListRegistriesReport is the report when querying for a sorted list of
// registries which may be contacted during certain operations.
type ListRegistriesReport struct {
Registries []string
}

View File

@ -121,12 +121,11 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti
}
var registryCreds *types.DockerAuthConfig
if options.Credentials != "" {
creds, err := util.ParseRegistryCreds(options.Credentials)
if err != nil {
return nil, err
if len(options.Username) > 0 && len(options.Password) > 0 {
registryCreds = &types.DockerAuthConfig{
Username: options.Username,
Password: options.Password,
}
registryCreds = creds
}
dockerRegistryOptions := image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,
@ -226,12 +225,11 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
}
var registryCreds *types.DockerAuthConfig
if options.Credentials != "" {
creds, err := util.ParseRegistryCreds(options.Credentials)
if err != nil {
return err
if len(options.Username) > 0 && len(options.Password) > 0 {
registryCreds = &types.DockerAuthConfig{
Username: options.Username,
Password: options.Password,
}
registryCreds = creds
}
dockerRegistryOptions := image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,

View File

@ -16,6 +16,7 @@ import (
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
libpodImage "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/util"
@ -179,12 +180,28 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, names []string, opts en
case "v2s2", "docker":
manifestType = manifest.DockerV2Schema2MediaType
default:
return errors.Errorf("unknown format %q. Choose on of the supported formats: 'oci' or 'v2s2'", opts.Format)
return errors.Errorf("unknown format %q. Choose one of the supported formats: 'oci' or 'v2s2'", opts.Format)
}
}
// Set the system context.
sys := ir.Libpod.SystemContext()
if sys != nil {
sys = &types.SystemContext{}
}
sys.AuthFilePath = opts.Authfile
sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify
if opts.Username != "" && opts.Password != "" {
sys.DockerAuthConfig = &types.DockerAuthConfig{
Username: opts.Username,
Password: opts.Password,
}
}
options := manifests.PushOptions{
Store: ir.Libpod.GetStore(),
SystemContext: ir.Libpod.SystemContext(),
SystemContext: sys,
ImageListSelection: cp.CopySpecificImages,
Instances: nil,
RemoveSignatures: opts.RemoveSignatures,

View File

@ -56,6 +56,9 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en
return nil, errors.Wrapf(err, "unable to read %q as YAML", path)
}
// NOTE: pkg/bindings/play is also parsing the file.
// A pkg/kube would be nice to refactor and abstract
// parts of the K8s-related code.
if podYAML.Kind != "Pod" {
return nil, errors.Errorf("invalid YAML kind: %q. Pod is the only supported Kubernetes YAML kind", podYAML.Kind)
}
@ -147,6 +150,13 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en
writer = os.Stderr
}
if len(options.Username) > 0 && len(options.Password) > 0 {
registryCreds = &types.DockerAuthConfig{
Username: options.Username,
Password: options.Password,
}
}
dockerRegistryOptions := image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,
DockerCertPath: options.CertDir,

View File

@ -213,6 +213,9 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo
if fs.Changed("hooks-dir") {
options = append(options, libpod.WithHooksDir(cfg.Engine.HooksDir...))
}
if fs.Changed("registries-conf") {
options = append(options, libpod.WithRegistriesConf(cfg.RegistriesConf))
}
// TODO flag to set CNI plugins dir?

View File

@ -190,6 +190,7 @@ func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest strin
if err != nil {
return nil, "", err
}
logrus.Debugf("Content-Type from manifest GET is %q", res.Header.Get("Content-Type"))
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, "", errors.Wrapf(client.HandleErrorResponse(res), "Error reading manifest %s in %s", tagOrDigest, s.physicalRef.ref.Name())

View File

@ -172,7 +172,7 @@ func (m *OCI1) Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*type
Architecture: v1.Architecture,
Os: v1.OS,
Layers: layerInfosToStrings(m.LayerInfos()),
Env: d1.Config.Env,
Env: v1.Config.Env,
}
return i, nil
}

View File

@ -7,6 +7,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/containers/image/v5/types"
@ -37,7 +38,12 @@ var (
xdgRuntimeDirPath = filepath.FromSlash("containers/auth.json")
dockerHomePath = filepath.FromSlash(".docker/config.json")
dockerLegacyHomePath = ".dockercfg"
nonLinuxAuthFilePath = filepath.FromSlash(".config/containers/auth.json")
// Note that the keyring support has been disabled as it was causing
// regressions. Before enabling, please revisit TODO(keyring) comments
// which need to be addressed if the need remerged to support the
// kernel keyring.
enableKeyring = false
// ErrNotLoggedIn is returned for users not logged into a registry
@ -73,6 +79,70 @@ func SetAuthentication(sys *types.SystemContext, registry, username, password st
})
}
// GetAllCredentials returns the registry credentials for all registries stored
// in either the auth.json file or the docker/config.json.
func GetAllCredentials(sys *types.SystemContext) (map[string]types.DockerAuthConfig, error) {
// Note: we need to read the auth files in the inverse order to prevent
// a priority inversion when writing to the map.
authConfigs := make(map[string]types.DockerAuthConfig)
paths := getAuthFilePaths(sys)
for i := len(paths) - 1; i >= 0; i-- {
path := paths[i]
// readJSONFile returns an empty map in case the path doesn't exist.
auths, err := readJSONFile(path.path, path.legacyFormat)
if err != nil {
return nil, errors.Wrapf(err, "error reading JSON file %q", path.path)
}
for registry, data := range auths.AuthConfigs {
conf, err := decodeDockerAuth(data)
if err != nil {
return nil, err
}
authConfigs[normalizeRegistry(registry)] = conf
}
// Credential helpers may override credentials from the auth file.
for registry, credHelper := range auths.CredHelpers {
username, password, err := getAuthFromCredHelper(credHelper, registry)
if err != nil {
if credentials.IsErrCredentialsNotFoundMessage(err.Error()) {
continue
}
return nil, err
}
conf := types.DockerAuthConfig{Username: username, Password: password}
authConfigs[normalizeRegistry(registry)] = conf
}
}
// TODO(keyring): if we ever reenable the keyring support, we had to
// query all credentials from the keyring here.
return authConfigs, nil
}
// getAuthFilePaths returns a slice of authPaths based on the system context
// in the order they should be searched. Note that some paths may not exist.
func getAuthFilePaths(sys *types.SystemContext) []authPath {
paths := []authPath{}
pathToAuth, lf, err := getPathToAuth(sys)
if err == nil {
paths = append(paths, authPath{path: pathToAuth, legacyFormat: lf})
} else {
// Error means that the path set for XDG_RUNTIME_DIR does not exist
// but we don't want to completely fail in the case that the user is pulling a public image
// Logging the error as a warning instead and moving on to pulling the image
logrus.Warnf("%v: Trying to pull image in the event that it is a public image.", err)
}
paths = append(paths,
authPath{path: filepath.Join(homedir.Get(), dockerHomePath), legacyFormat: false},
authPath{path: filepath.Join(homedir.Get(), dockerLegacyHomePath), legacyFormat: true},
)
return paths
}
// GetCredentials returns the registry credentials stored in either auth.json
// file or .docker/config.json, including support for OAuth2 and IdentityToken.
// If an entry is not found, an empty struct is returned.
@ -93,21 +163,7 @@ func GetCredentials(sys *types.SystemContext, registry string) (types.DockerAuth
}
}
paths := []authPath{}
pathToAuth, lf, err := getPathToAuth(sys)
if err == nil {
paths = append(paths, authPath{path: pathToAuth, legacyFormat: lf})
} else {
// Error means that the path set for XDG_RUNTIME_DIR does not exist
// but we don't want to completely fail in the case that the user is pulling a public image
// Logging the error as a warning instead and moving on to pulling the image
logrus.Warnf("%v: Trying to pull image in the event that it is a public image.", err)
}
paths = append(paths,
authPath{path: filepath.Join(homedir.Get(), dockerHomePath), legacyFormat: false},
authPath{path: filepath.Join(homedir.Get(), dockerLegacyHomePath), legacyFormat: true})
for _, path := range paths {
for _, path := range getAuthFilePaths(sys) {
authConfig, err := findAuthentication(registry, path.path, path.legacyFormat)
if err != nil {
logrus.Debugf("Credentials not found")
@ -189,10 +245,8 @@ func RemoveAllAuthentication(sys *types.SystemContext) error {
})
}
// getPath gets the path of the auth.json file
// The path can be overriden by the user if the overwrite-path flag is set
// If the flag is not set and XDG_RUNTIME_DIR is set, the auth.json file is saved in XDG_RUNTIME_DIR/containers
// Otherwise, the auth.json file is stored in /run/containers/UID
// getPathToAuth gets the path of the auth.json file used for reading and writting credentials
// returns the path, and a bool specifies whether the file is in legacy format
func getPathToAuth(sys *types.SystemContext) (string, bool, error) {
if sys != nil {
if sys.AuthFilePath != "" {
@ -205,6 +259,9 @@ func getPathToAuth(sys *types.SystemContext) (string, bool, error) {
return filepath.Join(sys.RootForImplicitAbsolutePaths, fmt.Sprintf(defaultPerUIDPathFormat, os.Getuid())), false, nil
}
}
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
return filepath.Join(homedir.Get(), nonLinuxAuthFilePath), false, nil
}
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
if runtimeDir != "" {
@ -248,6 +305,13 @@ func readJSONFile(path string, legacyFormat bool) (dockerConfigFile, error) {
return dockerConfigFile{}, errors.Wrapf(err, "error unmarshaling JSON at %q", path)
}
if auths.AuthConfigs == nil {
auths.AuthConfigs = map[string]dockerAuthConfig{}
}
if auths.CredHelpers == nil {
auths.CredHelpers = make(map[string]string)
}
return auths, nil
}
@ -257,17 +321,15 @@ func modifyJSON(sys *types.SystemContext, editor func(auths *dockerConfigFile) (
if err != nil {
return err
}
dir := filepath.Dir(path)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err = os.MkdirAll(dir, 0700); err != nil {
return errors.Wrapf(err, "error creating directory %q", dir)
}
}
if legacyFormat {
return fmt.Errorf("writes to %s using legacy format are not supported", path)
}
dir := filepath.Dir(path)
if err = os.MkdirAll(dir, 0700); err != nil {
return err
}
auths, err := readJSONFile(path, false)
if err != nil {
return errors.Wrapf(err, "error reading JSON file %q", path)

View File

@ -17,11 +17,13 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/types"
"github.com/containers/storage/pkg/homedir"
"github.com/pkg/errors"
)
@ -34,6 +36,9 @@ var systemDefaultPolicyPath = builtinDefaultPolicyPath
// DO NOT change this, instead see systemDefaultPolicyPath above.
const builtinDefaultPolicyPath = "/etc/containers/policy.json"
// userPolicyFile is the path to the per user policy path.
var userPolicyFile = filepath.FromSlash(".config/containers/policy.json")
// InvalidPolicyFormatError is returned when parsing an invalid policy configuration.
type InvalidPolicyFormatError string
@ -53,13 +58,15 @@ func DefaultPolicy(sys *types.SystemContext) (*Policy, error) {
// defaultPolicyPath returns a path to the default policy of the system.
func defaultPolicyPath(sys *types.SystemContext) string {
if sys != nil {
if sys.SignaturePolicyPath != "" {
return sys.SignaturePolicyPath
}
if sys.RootForImplicitAbsolutePaths != "" {
return filepath.Join(sys.RootForImplicitAbsolutePaths, systemDefaultPolicyPath)
}
if sys != nil && sys.SignaturePolicyPath != "" {
return sys.SignaturePolicyPath
}
userPolicyFilePath := filepath.Join(homedir.Get(), userPolicyFile)
if _, err := os.Stat(userPolicyFilePath); err == nil {
return userPolicyFilePath
}
if sys != nil && sys.RootForImplicitAbsolutePaths != "" {
return filepath.Join(sys.RootForImplicitAbsolutePaths, systemDefaultPolicyPath)
}
return systemDefaultPolicyPath
}

View File

@ -6,12 +6,12 @@ const (
// VersionMajor is for an API incompatible changes
VersionMajor = 5
// VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 4
VersionMinor = 5
// VersionPatch is for backwards-compatible bug fixes
VersionPatch = 4
VersionPatch = 0
// VersionDev indicates development branch. Releases will be empty string.
VersionDev = ""
VersionDev = "-dev"
)
// Version is the specification version that the package types support.

View File

@ -156,8 +156,12 @@ func (b *blockDec) reset(br byteBuffer, windowSize uint64) error {
}
return ErrCompressedSizeTooBig
}
default:
case blockTypeRaw:
b.RLESize = 0
// We do not need a destination for raw blocks.
maxSize = -1
default:
panic("Invalid block type")
}
// Read block data.

View File

@ -461,6 +461,7 @@ func (d *Decoder) startStreamDecoder(inStream chan decodeStream) {
br := readerWrapper{r: stream.r}
decodeStream:
for {
frame.history.reset()
err := frame.reset(&br)
if debug && err != nil {
println("Frame decoder returned", err)

View File

@ -64,6 +64,7 @@ type sequenceDecs struct {
hist []byte
literals []byte
out []byte
windowSize int
maxBits uint8
}
@ -82,6 +83,7 @@ func (s *sequenceDecs) initialize(br *bitReader, hist *history, literals, out []
s.hist = hist.b
s.prevOffset = hist.recentOffsets
s.maxBits = s.litLengths.fse.maxBits + s.offsets.fse.maxBits + s.matchLengths.fse.maxBits
s.windowSize = hist.windowSize
s.out = out
return nil
}
@ -131,6 +133,9 @@ func (s *sequenceDecs) decode(seqs int, br *bitReader, hist []byte) error {
if matchOff > len(s.out)+len(hist)+litLen {
return fmt.Errorf("match offset (%d) bigger than current history (%d)", matchOff, len(s.out)+len(hist)+litLen)
}
if matchOff > s.windowSize {
return fmt.Errorf("match offset (%d) bigger than window size (%d)", matchOff, s.windowSize)
}
if matchOff == 0 && matchLen > 0 {
return fmt.Errorf("zero matchoff and matchlen > 0")
}

14
vendor/github.com/mattn/go-isatty/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,14 @@
language: go
sudo: false
go:
- 1.13.x
- tip
before_install:
- go get -t -v ./...
script:
- ./go.test.sh
after_success:
- bash <(curl -s https://codecov.io/bash)

9
vendor/github.com/mattn/go-isatty/LICENSE generated vendored Normal file
View File

@ -0,0 +1,9 @@
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
MIT License (Expat)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

50
vendor/github.com/mattn/go-isatty/README.md generated vendored Normal file
View File

@ -0,0 +1,50 @@
# go-isatty
[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty)
[![Codecov](https://codecov.io/gh/mattn/go-isatty/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-isatty)
[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master)
[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty)
isatty for golang
## Usage
```go
package main
import (
"fmt"
"github.com/mattn/go-isatty"
"os"
)
func main() {
if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Println("Is Terminal")
} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
fmt.Println("Is Cygwin/MSYS2 Terminal")
} else {
fmt.Println("Is Not Terminal")
}
}
```
## Installation
```
$ go get github.com/mattn/go-isatty
```
## License
MIT
## Author
Yasuhiro Matsumoto (a.k.a mattn)
## Thanks
* k-takata: base idea for IsCygwinTerminal
https://github.com/k-takata/go-iscygpty

2
vendor/github.com/mattn/go-isatty/doc.go generated vendored Normal file
View File

@ -0,0 +1,2 @@
// Package isatty implements interface to isatty
package isatty

5
vendor/github.com/mattn/go-isatty/go.mod generated vendored Normal file
View File

@ -0,0 +1,5 @@
module github.com/mattn/go-isatty
go 1.12
require golang.org/x/sys v0.0.0-20200116001909-b77594299b42

2
vendor/github.com/mattn/go-isatty/go.sum generated vendored Normal file
View File

@ -0,0 +1,2 @@
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

12
vendor/github.com/mattn/go-isatty/go.test.sh generated vendored Normal file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
echo "" > coverage.txt
for d in $(go list ./... | grep -v vendor); do
go test -race -coverprofile=profile.out -covermode=atomic "$d"
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
fi
done

18
vendor/github.com/mattn/go-isatty/isatty_bsd.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
// +build darwin freebsd openbsd netbsd dragonfly
// +build !appengine
package isatty
import "golang.org/x/sys/unix"
// IsTerminal return true if the file descriptor is terminal.
func IsTerminal(fd uintptr) bool {
_, err := unix.IoctlGetTermios(int(fd), unix.TIOCGETA)
return err == nil
}
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}

15
vendor/github.com/mattn/go-isatty/isatty_others.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
// +build appengine js nacl
package isatty
// IsTerminal returns true if the file descriptor is terminal which
// is always false on js and appengine classic which is a sandboxed PaaS.
func IsTerminal(fd uintptr) bool {
return false
}
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}

22
vendor/github.com/mattn/go-isatty/isatty_plan9.go generated vendored Normal file
View File

@ -0,0 +1,22 @@
// +build plan9
package isatty
import (
"syscall"
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd uintptr) bool {
path, err := syscall.Fd2path(int(fd))
if err != nil {
return false
}
return path == "/dev/cons" || path == "/mnt/term/dev/cons"
}
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}

22
vendor/github.com/mattn/go-isatty/isatty_solaris.go generated vendored Normal file
View File

@ -0,0 +1,22 @@
// +build solaris
// +build !appengine
package isatty
import (
"golang.org/x/sys/unix"
)
// IsTerminal returns true if the given file descriptor is a terminal.
// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c
func IsTerminal(fd uintptr) bool {
var termio unix.Termio
err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio)
return err == nil
}
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}

18
vendor/github.com/mattn/go-isatty/isatty_tcgets.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
// +build linux aix
// +build !appengine
package isatty
import "golang.org/x/sys/unix"
// IsTerminal return true if the file descriptor is terminal.
func IsTerminal(fd uintptr) bool {
_, err := unix.IoctlGetTermios(int(fd), unix.TCGETS)
return err == nil
}
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}

125
vendor/github.com/mattn/go-isatty/isatty_windows.go generated vendored Normal file
View File

@ -0,0 +1,125 @@
// +build windows
// +build !appengine
package isatty
import (
"errors"
"strings"
"syscall"
"unicode/utf16"
"unsafe"
)
const (
objectNameInfo uintptr = 1
fileNameInfo = 2
fileTypePipe = 3
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
ntdll = syscall.NewLazyDLL("ntdll.dll")
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
procGetFileType = kernel32.NewProc("GetFileType")
procNtQueryObject = ntdll.NewProc("NtQueryObject")
)
func init() {
// Check if GetFileInformationByHandleEx is available.
if procGetFileInformationByHandleEx.Find() != nil {
procGetFileInformationByHandleEx = nil
}
}
// IsTerminal return true if the file descriptor is terminal.
func IsTerminal(fd uintptr) bool {
var st uint32
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0
}
// Check pipe name is used for cygwin/msys2 pty.
// Cygwin/MSYS2 PTY has a name like:
// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master
func isCygwinPipeName(name string) bool {
token := strings.Split(name, "-")
if len(token) < 5 {
return false
}
if token[0] != `\msys` &&
token[0] != `\cygwin` &&
token[0] != `\Device\NamedPipe\msys` &&
token[0] != `\Device\NamedPipe\cygwin` {
return false
}
if token[1] == "" {
return false
}
if !strings.HasPrefix(token[2], "pty") {
return false
}
if token[3] != `from` && token[3] != `to` {
return false
}
if token[4] != "master" {
return false
}
return true
}
// getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler
// since GetFileInformationByHandleEx is not avilable under windows Vista and still some old fashion
// guys are using Windows XP, this is a workaround for those guys, it will also work on system from
// Windows vista to 10
// see https://stackoverflow.com/a/18792477 for details
func getFileNameByHandle(fd uintptr) (string, error) {
if procNtQueryObject == nil {
return "", errors.New("ntdll.dll: NtQueryObject not supported")
}
var buf [4 + syscall.MAX_PATH]uint16
var result int
r, _, e := syscall.Syscall6(procNtQueryObject.Addr(), 5,
fd, objectNameInfo, uintptr(unsafe.Pointer(&buf)), uintptr(2*len(buf)), uintptr(unsafe.Pointer(&result)), 0)
if r != 0 {
return "", e
}
return string(utf16.Decode(buf[4 : 4+buf[0]/2])), nil
}
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
// terminal.
func IsCygwinTerminal(fd uintptr) bool {
if procGetFileInformationByHandleEx == nil {
name, err := getFileNameByHandle(fd)
if err != nil {
return false
}
return isCygwinPipeName(name)
}
// Cygwin/msys's pty is a pipe.
ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0)
if ft != fileTypePipe || e != 0 {
return false
}
var buf [2 + syscall.MAX_PATH]uint16
r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(),
4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)),
uintptr(len(buf)*2), 0, 0)
if r == 0 || e != 0 {
return false
}
l := *(*uint32)(unsafe.Pointer(&buf))
return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2])))
}

8
vendor/github.com/mattn/go-isatty/renovate.json generated vendored Normal file
View File

@ -0,0 +1,8 @@
{
"extends": [
"config:base"
],
"postUpdateOptions": [
"gomodTidy"
]
}

16
vendor/github.com/mattn/go-runewidth/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,16 @@
language: go
sudo: false
go:
- 1.13.x
- tip
before_install:
- go get -t -v ./...
script:
- go generate
- git diff --cached --exit-code
- ./go.test.sh
after_success:
- bash <(curl -s https://codecov.io/bash)

21
vendor/github.com/mattn/go-runewidth/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Yasuhiro Matsumoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

27
vendor/github.com/mattn/go-runewidth/README.md generated vendored Normal file
View File

@ -0,0 +1,27 @@
go-runewidth
============
[![Build Status](https://travis-ci.org/mattn/go-runewidth.png?branch=master)](https://travis-ci.org/mattn/go-runewidth)
[![Codecov](https://codecov.io/gh/mattn/go-runewidth/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-runewidth)
[![GoDoc](https://godoc.org/github.com/mattn/go-runewidth?status.svg)](http://godoc.org/github.com/mattn/go-runewidth)
[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-runewidth)](https://goreportcard.com/report/github.com/mattn/go-runewidth)
Provides functions to get fixed width of the character or string.
Usage
-----
```go
runewidth.StringWidth("つのだ☆HIRO") == 12
```
Author
------
Yasuhiro Matsumoto
License
-------
under the MIT License: http://mattn.mit-license.org/2013

3
vendor/github.com/mattn/go-runewidth/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/mattn/go-runewidth
go 1.9

12
vendor/github.com/mattn/go-runewidth/go.test.sh generated vendored Normal file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
echo "" > coverage.txt
for d in $(go list ./... | grep -v vendor); do
go test -race -coverprofile=profile.out -covermode=atomic "$d"
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
fi
done

257
vendor/github.com/mattn/go-runewidth/runewidth.go generated vendored Normal file
View File

@ -0,0 +1,257 @@
package runewidth
import (
"os"
)
//go:generate go run script/generate.go
var (
// EastAsianWidth will be set true if the current locale is CJK
EastAsianWidth bool
// ZeroWidthJoiner is flag to set to use UTR#51 ZWJ
ZeroWidthJoiner bool
// DefaultCondition is a condition in current locale
DefaultCondition = &Condition{}
)
func init() {
handleEnv()
}
func handleEnv() {
env := os.Getenv("RUNEWIDTH_EASTASIAN")
if env == "" {
EastAsianWidth = IsEastAsian()
} else {
EastAsianWidth = env == "1"
}
// update DefaultCondition
DefaultCondition.EastAsianWidth = EastAsianWidth
DefaultCondition.ZeroWidthJoiner = ZeroWidthJoiner
}
type interval struct {
first rune
last rune
}
type table []interval
func inTables(r rune, ts ...table) bool {
for _, t := range ts {
if inTable(r, t) {
return true
}
}
return false
}
func inTable(r rune, t table) bool {
if r < t[0].first {
return false
}
bot := 0
top := len(t) - 1
for top >= bot {
mid := (bot + top) >> 1
switch {
case t[mid].last < r:
bot = mid + 1
case t[mid].first > r:
top = mid - 1
default:
return true
}
}
return false
}
var private = table{
{0x00E000, 0x00F8FF}, {0x0F0000, 0x0FFFFD}, {0x100000, 0x10FFFD},
}
var nonprint = table{
{0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
{0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
{0x2028, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
}
// Condition have flag EastAsianWidth whether the current locale is CJK or not.
type Condition struct {
EastAsianWidth bool
ZeroWidthJoiner bool
}
// NewCondition return new instance of Condition which is current locale.
func NewCondition() *Condition {
return &Condition{
EastAsianWidth: EastAsianWidth,
ZeroWidthJoiner: ZeroWidthJoiner,
}
}
// RuneWidth returns the number of cells in r.
// See http://www.unicode.org/reports/tr11/
func (c *Condition) RuneWidth(r rune) int {
switch {
case r < 0 || r > 0x10FFFF || inTables(r, nonprint, combining, notassigned):
return 0
case (c.EastAsianWidth && IsAmbiguousWidth(r)) || inTables(r, doublewidth):
return 2
default:
return 1
}
}
func (c *Condition) stringWidth(s string) (width int) {
for _, r := range []rune(s) {
width += c.RuneWidth(r)
}
return width
}
func (c *Condition) stringWidthZeroJoiner(s string) (width int) {
r1, r2 := rune(0), rune(0)
for _, r := range []rune(s) {
if r == 0xFE0E || r == 0xFE0F {
continue
}
w := c.RuneWidth(r)
if r2 == 0x200D && inTables(r, emoji) && inTables(r1, emoji) {
if width < w {
width = w
}
} else {
width += w
}
r1, r2 = r2, r
}
return width
}
// StringWidth return width as you can see
func (c *Condition) StringWidth(s string) (width int) {
if c.ZeroWidthJoiner {
return c.stringWidthZeroJoiner(s)
}
return c.stringWidth(s)
}
// Truncate return string truncated with w cells
func (c *Condition) Truncate(s string, w int, tail string) string {
if c.StringWidth(s) <= w {
return s
}
r := []rune(s)
tw := c.StringWidth(tail)
w -= tw
width := 0
i := 0
for ; i < len(r); i++ {
cw := c.RuneWidth(r[i])
if width+cw > w {
break
}
width += cw
}
return string(r[0:i]) + tail
}
// Wrap return string wrapped with w cells
func (c *Condition) Wrap(s string, w int) string {
width := 0
out := ""
for _, r := range []rune(s) {
cw := RuneWidth(r)
if r == '\n' {
out += string(r)
width = 0
continue
} else if width+cw > w {
out += "\n"
width = 0
out += string(r)
width += cw
continue
}
out += string(r)
width += cw
}
return out
}
// FillLeft return string filled in left by spaces in w cells
func (c *Condition) FillLeft(s string, w int) string {
width := c.StringWidth(s)
count := w - width
if count > 0 {
b := make([]byte, count)
for i := range b {
b[i] = ' '
}
return string(b) + s
}
return s
}
// FillRight return string filled in left by spaces in w cells
func (c *Condition) FillRight(s string, w int) string {
width := c.StringWidth(s)
count := w - width
if count > 0 {
b := make([]byte, count)
for i := range b {
b[i] = ' '
}
return s + string(b)
}
return s
}
// RuneWidth returns the number of cells in r.
// See http://www.unicode.org/reports/tr11/
func RuneWidth(r rune) int {
return DefaultCondition.RuneWidth(r)
}
// IsAmbiguousWidth returns whether is ambiguous width or not.
func IsAmbiguousWidth(r rune) bool {
return inTables(r, private, ambiguous)
}
// IsNeutralWidth returns whether is neutral width or not.
func IsNeutralWidth(r rune) bool {
return inTable(r, neutral)
}
// StringWidth return width as you can see
func StringWidth(s string) (width int) {
return DefaultCondition.StringWidth(s)
}
// Truncate return string truncated with w cells
func Truncate(s string, w int, tail string) string {
return DefaultCondition.Truncate(s, w, tail)
}
// Wrap return string wrapped with w cells
func Wrap(s string, w int) string {
return DefaultCondition.Wrap(s, w)
}
// FillLeft return string filled in left by spaces in w cells
func FillLeft(s string, w int) string {
return DefaultCondition.FillLeft(s, w)
}
// FillRight return string filled in left by spaces in w cells
func FillRight(s string, w int) string {
return DefaultCondition.FillRight(s, w)
}

View File

@ -0,0 +1,8 @@
// +build appengine
package runewidth
// IsEastAsian return true if the current locale is CJK
func IsEastAsian() bool {
return false
}

9
vendor/github.com/mattn/go-runewidth/runewidth_js.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
// +build js
// +build !appengine
package runewidth
func IsEastAsian() bool {
// TODO: Implement this for the web. Detect east asian in a compatible way, and return true.
return false
}

View File

@ -0,0 +1,82 @@
// +build !windows
// +build !js
// +build !appengine
package runewidth
import (
"os"
"regexp"
"strings"
)
var reLoc = regexp.MustCompile(`^[a-z][a-z][a-z]?(?:_[A-Z][A-Z])?\.(.+)`)
var mblenTable = map[string]int{
"utf-8": 6,
"utf8": 6,
"jis": 8,
"eucjp": 3,
"euckr": 2,
"euccn": 2,
"sjis": 2,
"cp932": 2,
"cp51932": 2,
"cp936": 2,
"cp949": 2,
"cp950": 2,
"big5": 2,
"gbk": 2,
"gb2312": 2,
}
func isEastAsian(locale string) bool {
charset := strings.ToLower(locale)
r := reLoc.FindStringSubmatch(locale)
if len(r) == 2 {
charset = strings.ToLower(r[1])
}
if strings.HasSuffix(charset, "@cjk_narrow") {
return false
}
for pos, b := range []byte(charset) {
if b == '@' {
charset = charset[:pos]
break
}
}
max := 1
if m, ok := mblenTable[charset]; ok {
max = m
}
if max > 1 && (charset[0] != 'u' ||
strings.HasPrefix(locale, "ja") ||
strings.HasPrefix(locale, "ko") ||
strings.HasPrefix(locale, "zh")) {
return true
}
return false
}
// IsEastAsian return true if the current locale is CJK
func IsEastAsian() bool {
locale := os.Getenv("LC_ALL")
if locale == "" {
locale = os.Getenv("LC_CTYPE")
}
if locale == "" {
locale = os.Getenv("LANG")
}
// ignore C locale
if locale == "POSIX" || locale == "C" {
return false
}
if len(locale) > 1 && locale[0] == 'C' && (locale[1] == '.' || locale[1] == '-') {
return false
}
return isEastAsian(locale)
}

437
vendor/github.com/mattn/go-runewidth/runewidth_table.go generated vendored Normal file
View File

@ -0,0 +1,437 @@
// Code generated by script/generate.go. DO NOT EDIT.
package runewidth
var combining = table{
{0x0300, 0x036F}, {0x0483, 0x0489}, {0x07EB, 0x07F3},
{0x0C00, 0x0C00}, {0x0C04, 0x0C04}, {0x0D00, 0x0D01},
{0x135D, 0x135F}, {0x1A7F, 0x1A7F}, {0x1AB0, 0x1AC0},
{0x1B6B, 0x1B73}, {0x1DC0, 0x1DF9}, {0x1DFB, 0x1DFF},
{0x20D0, 0x20F0}, {0x2CEF, 0x2CF1}, {0x2DE0, 0x2DFF},
{0x3099, 0x309A}, {0xA66F, 0xA672}, {0xA674, 0xA67D},
{0xA69E, 0xA69F}, {0xA6F0, 0xA6F1}, {0xA8E0, 0xA8F1},
{0xFE20, 0xFE2F}, {0x101FD, 0x101FD}, {0x10376, 0x1037A},
{0x10EAB, 0x10EAC}, {0x10F46, 0x10F50}, {0x11300, 0x11301},
{0x1133B, 0x1133C}, {0x11366, 0x1136C}, {0x11370, 0x11374},
{0x16AF0, 0x16AF4}, {0x1D165, 0x1D169}, {0x1D16D, 0x1D172},
{0x1D17B, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD},
{0x1D242, 0x1D244}, {0x1E000, 0x1E006}, {0x1E008, 0x1E018},
{0x1E01B, 0x1E021}, {0x1E023, 0x1E024}, {0x1E026, 0x1E02A},
{0x1E8D0, 0x1E8D6},
}
var doublewidth = table{
{0x1100, 0x115F}, {0x231A, 0x231B}, {0x2329, 0x232A},
{0x23E9, 0x23EC}, {0x23F0, 0x23F0}, {0x23F3, 0x23F3},
{0x25FD, 0x25FE}, {0x2614, 0x2615}, {0x2648, 0x2653},
{0x267F, 0x267F}, {0x2693, 0x2693}, {0x26A1, 0x26A1},
{0x26AA, 0x26AB}, {0x26BD, 0x26BE}, {0x26C4, 0x26C5},
{0x26CE, 0x26CE}, {0x26D4, 0x26D4}, {0x26EA, 0x26EA},
{0x26F2, 0x26F3}, {0x26F5, 0x26F5}, {0x26FA, 0x26FA},
{0x26FD, 0x26FD}, {0x2705, 0x2705}, {0x270A, 0x270B},
{0x2728, 0x2728}, {0x274C, 0x274C}, {0x274E, 0x274E},
{0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
{0x27B0, 0x27B0}, {0x27BF, 0x27BF}, {0x2B1B, 0x2B1C},
{0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x2E80, 0x2E99},
{0x2E9B, 0x2EF3}, {0x2F00, 0x2FD5}, {0x2FF0, 0x2FFB},
{0x3000, 0x303E}, {0x3041, 0x3096}, {0x3099, 0x30FF},
{0x3105, 0x312F}, {0x3131, 0x318E}, {0x3190, 0x31E3},
{0x31F0, 0x321E}, {0x3220, 0x3247}, {0x3250, 0x4DBF},
{0x4E00, 0xA48C}, {0xA490, 0xA4C6}, {0xA960, 0xA97C},
{0xAC00, 0xD7A3}, {0xF900, 0xFAFF}, {0xFE10, 0xFE19},
{0xFE30, 0xFE52}, {0xFE54, 0xFE66}, {0xFE68, 0xFE6B},
{0xFF01, 0xFF60}, {0xFFE0, 0xFFE6}, {0x16FE0, 0x16FE4},
{0x16FF0, 0x16FF1}, {0x17000, 0x187F7}, {0x18800, 0x18CD5},
{0x18D00, 0x18D08}, {0x1B000, 0x1B11E}, {0x1B150, 0x1B152},
{0x1B164, 0x1B167}, {0x1B170, 0x1B2FB}, {0x1F004, 0x1F004},
{0x1F0CF, 0x1F0CF}, {0x1F18E, 0x1F18E}, {0x1F191, 0x1F19A},
{0x1F200, 0x1F202}, {0x1F210, 0x1F23B}, {0x1F240, 0x1F248},
{0x1F250, 0x1F251}, {0x1F260, 0x1F265}, {0x1F300, 0x1F320},
{0x1F32D, 0x1F335}, {0x1F337, 0x1F37C}, {0x1F37E, 0x1F393},
{0x1F3A0, 0x1F3CA}, {0x1F3CF, 0x1F3D3}, {0x1F3E0, 0x1F3F0},
{0x1F3F4, 0x1F3F4}, {0x1F3F8, 0x1F43E}, {0x1F440, 0x1F440},
{0x1F442, 0x1F4FC}, {0x1F4FF, 0x1F53D}, {0x1F54B, 0x1F54E},
{0x1F550, 0x1F567}, {0x1F57A, 0x1F57A}, {0x1F595, 0x1F596},
{0x1F5A4, 0x1F5A4}, {0x1F5FB, 0x1F64F}, {0x1F680, 0x1F6C5},
{0x1F6CC, 0x1F6CC}, {0x1F6D0, 0x1F6D2}, {0x1F6D5, 0x1F6D7},
{0x1F6EB, 0x1F6EC}, {0x1F6F4, 0x1F6FC}, {0x1F7E0, 0x1F7EB},
{0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1F978},
{0x1F97A, 0x1F9CB}, {0x1F9CD, 0x1F9FF}, {0x1FA70, 0x1FA74},
{0x1FA78, 0x1FA7A}, {0x1FA80, 0x1FA86}, {0x1FA90, 0x1FAA8},
{0x1FAB0, 0x1FAB6}, {0x1FAC0, 0x1FAC2}, {0x1FAD0, 0x1FAD6},
{0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
}
var ambiguous = table{
{0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8},
{0x00AA, 0x00AA}, {0x00AD, 0x00AE}, {0x00B0, 0x00B4},
{0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6},
{0x00D0, 0x00D0}, {0x00D7, 0x00D8}, {0x00DE, 0x00E1},
{0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED},
{0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA},
{0x00FC, 0x00FC}, {0x00FE, 0x00FE}, {0x0101, 0x0101},
{0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B},
{0x0126, 0x0127}, {0x012B, 0x012B}, {0x0131, 0x0133},
{0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144},
{0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153},
{0x0166, 0x0167}, {0x016B, 0x016B}, {0x01CE, 0x01CE},
{0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4},
{0x01D6, 0x01D6}, {0x01D8, 0x01D8}, {0x01DA, 0x01DA},
{0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261},
{0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB},
{0x02CD, 0x02CD}, {0x02D0, 0x02D0}, {0x02D8, 0x02DB},
{0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0300, 0x036F},
{0x0391, 0x03A1}, {0x03A3, 0x03A9}, {0x03B1, 0x03C1},
{0x03C3, 0x03C9}, {0x0401, 0x0401}, {0x0410, 0x044F},
{0x0451, 0x0451}, {0x2010, 0x2010}, {0x2013, 0x2016},
{0x2018, 0x2019}, {0x201C, 0x201D}, {0x2020, 0x2022},
{0x2024, 0x2027}, {0x2030, 0x2030}, {0x2032, 0x2033},
{0x2035, 0x2035}, {0x203B, 0x203B}, {0x203E, 0x203E},
{0x2074, 0x2074}, {0x207F, 0x207F}, {0x2081, 0x2084},
{0x20AC, 0x20AC}, {0x2103, 0x2103}, {0x2105, 0x2105},
{0x2109, 0x2109}, {0x2113, 0x2113}, {0x2116, 0x2116},
{0x2121, 0x2122}, {0x2126, 0x2126}, {0x212B, 0x212B},
{0x2153, 0x2154}, {0x215B, 0x215E}, {0x2160, 0x216B},
{0x2170, 0x2179}, {0x2189, 0x2189}, {0x2190, 0x2199},
{0x21B8, 0x21B9}, {0x21D2, 0x21D2}, {0x21D4, 0x21D4},
{0x21E7, 0x21E7}, {0x2200, 0x2200}, {0x2202, 0x2203},
{0x2207, 0x2208}, {0x220B, 0x220B}, {0x220F, 0x220F},
{0x2211, 0x2211}, {0x2215, 0x2215}, {0x221A, 0x221A},
{0x221D, 0x2220}, {0x2223, 0x2223}, {0x2225, 0x2225},
{0x2227, 0x222C}, {0x222E, 0x222E}, {0x2234, 0x2237},
{0x223C, 0x223D}, {0x2248, 0x2248}, {0x224C, 0x224C},
{0x2252, 0x2252}, {0x2260, 0x2261}, {0x2264, 0x2267},
{0x226A, 0x226B}, {0x226E, 0x226F}, {0x2282, 0x2283},
{0x2286, 0x2287}, {0x2295, 0x2295}, {0x2299, 0x2299},
{0x22A5, 0x22A5}, {0x22BF, 0x22BF}, {0x2312, 0x2312},
{0x2460, 0x24E9}, {0x24EB, 0x254B}, {0x2550, 0x2573},
{0x2580, 0x258F}, {0x2592, 0x2595}, {0x25A0, 0x25A1},
{0x25A3, 0x25A9}, {0x25B2, 0x25B3}, {0x25B6, 0x25B7},
{0x25BC, 0x25BD}, {0x25C0, 0x25C1}, {0x25C6, 0x25C8},
{0x25CB, 0x25CB}, {0x25CE, 0x25D1}, {0x25E2, 0x25E5},
{0x25EF, 0x25EF}, {0x2605, 0x2606}, {0x2609, 0x2609},
{0x260E, 0x260F}, {0x261C, 0x261C}, {0x261E, 0x261E},
{0x2640, 0x2640}, {0x2642, 0x2642}, {0x2660, 0x2661},
{0x2663, 0x2665}, {0x2667, 0x266A}, {0x266C, 0x266D},
{0x266F, 0x266F}, {0x269E, 0x269F}, {0x26BF, 0x26BF},
{0x26C6, 0x26CD}, {0x26CF, 0x26D3}, {0x26D5, 0x26E1},
{0x26E3, 0x26E3}, {0x26E8, 0x26E9}, {0x26EB, 0x26F1},
{0x26F4, 0x26F4}, {0x26F6, 0x26F9}, {0x26FB, 0x26FC},
{0x26FE, 0x26FF}, {0x273D, 0x273D}, {0x2776, 0x277F},
{0x2B56, 0x2B59}, {0x3248, 0x324F}, {0xE000, 0xF8FF},
{0xFE00, 0xFE0F}, {0xFFFD, 0xFFFD}, {0x1F100, 0x1F10A},
{0x1F110, 0x1F12D}, {0x1F130, 0x1F169}, {0x1F170, 0x1F18D},
{0x1F18F, 0x1F190}, {0x1F19B, 0x1F1AC}, {0xE0100, 0xE01EF},
{0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD},
}
var notassigned = table{
{0x27E6, 0x27ED}, {0x2985, 0x2986},
}
var neutral = table{
{0x0000, 0x001F}, {0x007F, 0x00A0}, {0x00A9, 0x00A9},
{0x00AB, 0x00AB}, {0x00B5, 0x00B5}, {0x00BB, 0x00BB},
{0x00C0, 0x00C5}, {0x00C7, 0x00CF}, {0x00D1, 0x00D6},
{0x00D9, 0x00DD}, {0x00E2, 0x00E5}, {0x00E7, 0x00E7},
{0x00EB, 0x00EB}, {0x00EE, 0x00EF}, {0x00F1, 0x00F1},
{0x00F4, 0x00F6}, {0x00FB, 0x00FB}, {0x00FD, 0x00FD},
{0x00FF, 0x0100}, {0x0102, 0x0110}, {0x0112, 0x0112},
{0x0114, 0x011A}, {0x011C, 0x0125}, {0x0128, 0x012A},
{0x012C, 0x0130}, {0x0134, 0x0137}, {0x0139, 0x013E},
{0x0143, 0x0143}, {0x0145, 0x0147}, {0x014C, 0x014C},
{0x014E, 0x0151}, {0x0154, 0x0165}, {0x0168, 0x016A},
{0x016C, 0x01CD}, {0x01CF, 0x01CF}, {0x01D1, 0x01D1},
{0x01D3, 0x01D3}, {0x01D5, 0x01D5}, {0x01D7, 0x01D7},
{0x01D9, 0x01D9}, {0x01DB, 0x01DB}, {0x01DD, 0x0250},
{0x0252, 0x0260}, {0x0262, 0x02C3}, {0x02C5, 0x02C6},
{0x02C8, 0x02C8}, {0x02CC, 0x02CC}, {0x02CE, 0x02CF},
{0x02D1, 0x02D7}, {0x02DC, 0x02DC}, {0x02DE, 0x02DE},
{0x02E0, 0x02FF}, {0x0370, 0x0377}, {0x037A, 0x037F},
{0x0384, 0x038A}, {0x038C, 0x038C}, {0x038E, 0x0390},
{0x03AA, 0x03B0}, {0x03C2, 0x03C2}, {0x03CA, 0x0400},
{0x0402, 0x040F}, {0x0450, 0x0450}, {0x0452, 0x052F},
{0x0531, 0x0556}, {0x0559, 0x058A}, {0x058D, 0x058F},
{0x0591, 0x05C7}, {0x05D0, 0x05EA}, {0x05EF, 0x05F4},
{0x0600, 0x061C}, {0x061E, 0x070D}, {0x070F, 0x074A},
{0x074D, 0x07B1}, {0x07C0, 0x07FA}, {0x07FD, 0x082D},
{0x0830, 0x083E}, {0x0840, 0x085B}, {0x085E, 0x085E},
{0x0860, 0x086A}, {0x08A0, 0x08B4}, {0x08B6, 0x08C7},
{0x08D3, 0x0983}, {0x0985, 0x098C}, {0x098F, 0x0990},
{0x0993, 0x09A8}, {0x09AA, 0x09B0}, {0x09B2, 0x09B2},
{0x09B6, 0x09B9}, {0x09BC, 0x09C4}, {0x09C7, 0x09C8},
{0x09CB, 0x09CE}, {0x09D7, 0x09D7}, {0x09DC, 0x09DD},
{0x09DF, 0x09E3}, {0x09E6, 0x09FE}, {0x0A01, 0x0A03},
{0x0A05, 0x0A0A}, {0x0A0F, 0x0A10}, {0x0A13, 0x0A28},
{0x0A2A, 0x0A30}, {0x0A32, 0x0A33}, {0x0A35, 0x0A36},
{0x0A38, 0x0A39}, {0x0A3C, 0x0A3C}, {0x0A3E, 0x0A42},
{0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A51, 0x0A51},
{0x0A59, 0x0A5C}, {0x0A5E, 0x0A5E}, {0x0A66, 0x0A76},
{0x0A81, 0x0A83}, {0x0A85, 0x0A8D}, {0x0A8F, 0x0A91},
{0x0A93, 0x0AA8}, {0x0AAA, 0x0AB0}, {0x0AB2, 0x0AB3},
{0x0AB5, 0x0AB9}, {0x0ABC, 0x0AC5}, {0x0AC7, 0x0AC9},
{0x0ACB, 0x0ACD}, {0x0AD0, 0x0AD0}, {0x0AE0, 0x0AE3},
{0x0AE6, 0x0AF1}, {0x0AF9, 0x0AFF}, {0x0B01, 0x0B03},
{0x0B05, 0x0B0C}, {0x0B0F, 0x0B10}, {0x0B13, 0x0B28},
{0x0B2A, 0x0B30}, {0x0B32, 0x0B33}, {0x0B35, 0x0B39},
{0x0B3C, 0x0B44}, {0x0B47, 0x0B48}, {0x0B4B, 0x0B4D},
{0x0B55, 0x0B57}, {0x0B5C, 0x0B5D}, {0x0B5F, 0x0B63},
{0x0B66, 0x0B77}, {0x0B82, 0x0B83}, {0x0B85, 0x0B8A},
{0x0B8E, 0x0B90}, {0x0B92, 0x0B95}, {0x0B99, 0x0B9A},
{0x0B9C, 0x0B9C}, {0x0B9E, 0x0B9F}, {0x0BA3, 0x0BA4},
{0x0BA8, 0x0BAA}, {0x0BAE, 0x0BB9}, {0x0BBE, 0x0BC2},
{0x0BC6, 0x0BC8}, {0x0BCA, 0x0BCD}, {0x0BD0, 0x0BD0},
{0x0BD7, 0x0BD7}, {0x0BE6, 0x0BFA}, {0x0C00, 0x0C0C},
{0x0C0E, 0x0C10}, {0x0C12, 0x0C28}, {0x0C2A, 0x0C39},
{0x0C3D, 0x0C44}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D},
{0x0C55, 0x0C56}, {0x0C58, 0x0C5A}, {0x0C60, 0x0C63},
{0x0C66, 0x0C6F}, {0x0C77, 0x0C8C}, {0x0C8E, 0x0C90},
{0x0C92, 0x0CA8}, {0x0CAA, 0x0CB3}, {0x0CB5, 0x0CB9},
{0x0CBC, 0x0CC4}, {0x0CC6, 0x0CC8}, {0x0CCA, 0x0CCD},
{0x0CD5, 0x0CD6}, {0x0CDE, 0x0CDE}, {0x0CE0, 0x0CE3},
{0x0CE6, 0x0CEF}, {0x0CF1, 0x0CF2}, {0x0D00, 0x0D0C},
{0x0D0E, 0x0D10}, {0x0D12, 0x0D44}, {0x0D46, 0x0D48},
{0x0D4A, 0x0D4F}, {0x0D54, 0x0D63}, {0x0D66, 0x0D7F},
{0x0D81, 0x0D83}, {0x0D85, 0x0D96}, {0x0D9A, 0x0DB1},
{0x0DB3, 0x0DBB}, {0x0DBD, 0x0DBD}, {0x0DC0, 0x0DC6},
{0x0DCA, 0x0DCA}, {0x0DCF, 0x0DD4}, {0x0DD6, 0x0DD6},
{0x0DD8, 0x0DDF}, {0x0DE6, 0x0DEF}, {0x0DF2, 0x0DF4},
{0x0E01, 0x0E3A}, {0x0E3F, 0x0E5B}, {0x0E81, 0x0E82},
{0x0E84, 0x0E84}, {0x0E86, 0x0E8A}, {0x0E8C, 0x0EA3},
{0x0EA5, 0x0EA5}, {0x0EA7, 0x0EBD}, {0x0EC0, 0x0EC4},
{0x0EC6, 0x0EC6}, {0x0EC8, 0x0ECD}, {0x0ED0, 0x0ED9},
{0x0EDC, 0x0EDF}, {0x0F00, 0x0F47}, {0x0F49, 0x0F6C},
{0x0F71, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FBE, 0x0FCC},
{0x0FCE, 0x0FDA}, {0x1000, 0x10C5}, {0x10C7, 0x10C7},
{0x10CD, 0x10CD}, {0x10D0, 0x10FF}, {0x1160, 0x1248},
{0x124A, 0x124D}, {0x1250, 0x1256}, {0x1258, 0x1258},
{0x125A, 0x125D}, {0x1260, 0x1288}, {0x128A, 0x128D},
{0x1290, 0x12B0}, {0x12B2, 0x12B5}, {0x12B8, 0x12BE},
{0x12C0, 0x12C0}, {0x12C2, 0x12C5}, {0x12C8, 0x12D6},
{0x12D8, 0x1310}, {0x1312, 0x1315}, {0x1318, 0x135A},
{0x135D, 0x137C}, {0x1380, 0x1399}, {0x13A0, 0x13F5},
{0x13F8, 0x13FD}, {0x1400, 0x169C}, {0x16A0, 0x16F8},
{0x1700, 0x170C}, {0x170E, 0x1714}, {0x1720, 0x1736},
{0x1740, 0x1753}, {0x1760, 0x176C}, {0x176E, 0x1770},
{0x1772, 0x1773}, {0x1780, 0x17DD}, {0x17E0, 0x17E9},
{0x17F0, 0x17F9}, {0x1800, 0x180E}, {0x1810, 0x1819},
{0x1820, 0x1878}, {0x1880, 0x18AA}, {0x18B0, 0x18F5},
{0x1900, 0x191E}, {0x1920, 0x192B}, {0x1930, 0x193B},
{0x1940, 0x1940}, {0x1944, 0x196D}, {0x1970, 0x1974},
{0x1980, 0x19AB}, {0x19B0, 0x19C9}, {0x19D0, 0x19DA},
{0x19DE, 0x1A1B}, {0x1A1E, 0x1A5E}, {0x1A60, 0x1A7C},
{0x1A7F, 0x1A89}, {0x1A90, 0x1A99}, {0x1AA0, 0x1AAD},
{0x1AB0, 0x1AC0}, {0x1B00, 0x1B4B}, {0x1B50, 0x1B7C},
{0x1B80, 0x1BF3}, {0x1BFC, 0x1C37}, {0x1C3B, 0x1C49},
{0x1C4D, 0x1C88}, {0x1C90, 0x1CBA}, {0x1CBD, 0x1CC7},
{0x1CD0, 0x1CFA}, {0x1D00, 0x1DF9}, {0x1DFB, 0x1F15},
{0x1F18, 0x1F1D}, {0x1F20, 0x1F45}, {0x1F48, 0x1F4D},
{0x1F50, 0x1F57}, {0x1F59, 0x1F59}, {0x1F5B, 0x1F5B},
{0x1F5D, 0x1F5D}, {0x1F5F, 0x1F7D}, {0x1F80, 0x1FB4},
{0x1FB6, 0x1FC4}, {0x1FC6, 0x1FD3}, {0x1FD6, 0x1FDB},
{0x1FDD, 0x1FEF}, {0x1FF2, 0x1FF4}, {0x1FF6, 0x1FFE},
{0x2000, 0x200F}, {0x2011, 0x2012}, {0x2017, 0x2017},
{0x201A, 0x201B}, {0x201E, 0x201F}, {0x2023, 0x2023},
{0x2028, 0x202F}, {0x2031, 0x2031}, {0x2034, 0x2034},
{0x2036, 0x203A}, {0x203C, 0x203D}, {0x203F, 0x2064},
{0x2066, 0x2071}, {0x2075, 0x207E}, {0x2080, 0x2080},
{0x2085, 0x208E}, {0x2090, 0x209C}, {0x20A0, 0x20A8},
{0x20AA, 0x20AB}, {0x20AD, 0x20BF}, {0x20D0, 0x20F0},
{0x2100, 0x2102}, {0x2104, 0x2104}, {0x2106, 0x2108},
{0x210A, 0x2112}, {0x2114, 0x2115}, {0x2117, 0x2120},
{0x2123, 0x2125}, {0x2127, 0x212A}, {0x212C, 0x2152},
{0x2155, 0x215A}, {0x215F, 0x215F}, {0x216C, 0x216F},
{0x217A, 0x2188}, {0x218A, 0x218B}, {0x219A, 0x21B7},
{0x21BA, 0x21D1}, {0x21D3, 0x21D3}, {0x21D5, 0x21E6},
{0x21E8, 0x21FF}, {0x2201, 0x2201}, {0x2204, 0x2206},
{0x2209, 0x220A}, {0x220C, 0x220E}, {0x2210, 0x2210},
{0x2212, 0x2214}, {0x2216, 0x2219}, {0x221B, 0x221C},
{0x2221, 0x2222}, {0x2224, 0x2224}, {0x2226, 0x2226},
{0x222D, 0x222D}, {0x222F, 0x2233}, {0x2238, 0x223B},
{0x223E, 0x2247}, {0x2249, 0x224B}, {0x224D, 0x2251},
{0x2253, 0x225F}, {0x2262, 0x2263}, {0x2268, 0x2269},
{0x226C, 0x226D}, {0x2270, 0x2281}, {0x2284, 0x2285},
{0x2288, 0x2294}, {0x2296, 0x2298}, {0x229A, 0x22A4},
{0x22A6, 0x22BE}, {0x22C0, 0x2311}, {0x2313, 0x2319},
{0x231C, 0x2328}, {0x232B, 0x23E8}, {0x23ED, 0x23EF},
{0x23F1, 0x23F2}, {0x23F4, 0x2426}, {0x2440, 0x244A},
{0x24EA, 0x24EA}, {0x254C, 0x254F}, {0x2574, 0x257F},
{0x2590, 0x2591}, {0x2596, 0x259F}, {0x25A2, 0x25A2},
{0x25AA, 0x25B1}, {0x25B4, 0x25B5}, {0x25B8, 0x25BB},
{0x25BE, 0x25BF}, {0x25C2, 0x25C5}, {0x25C9, 0x25CA},
{0x25CC, 0x25CD}, {0x25D2, 0x25E1}, {0x25E6, 0x25EE},
{0x25F0, 0x25FC}, {0x25FF, 0x2604}, {0x2607, 0x2608},
{0x260A, 0x260D}, {0x2610, 0x2613}, {0x2616, 0x261B},
{0x261D, 0x261D}, {0x261F, 0x263F}, {0x2641, 0x2641},
{0x2643, 0x2647}, {0x2654, 0x265F}, {0x2662, 0x2662},
{0x2666, 0x2666}, {0x266B, 0x266B}, {0x266E, 0x266E},
{0x2670, 0x267E}, {0x2680, 0x2692}, {0x2694, 0x269D},
{0x26A0, 0x26A0}, {0x26A2, 0x26A9}, {0x26AC, 0x26BC},
{0x26C0, 0x26C3}, {0x26E2, 0x26E2}, {0x26E4, 0x26E7},
{0x2700, 0x2704}, {0x2706, 0x2709}, {0x270C, 0x2727},
{0x2729, 0x273C}, {0x273E, 0x274B}, {0x274D, 0x274D},
{0x274F, 0x2752}, {0x2756, 0x2756}, {0x2758, 0x2775},
{0x2780, 0x2794}, {0x2798, 0x27AF}, {0x27B1, 0x27BE},
{0x27C0, 0x27E5}, {0x27EE, 0x2984}, {0x2987, 0x2B1A},
{0x2B1D, 0x2B4F}, {0x2B51, 0x2B54}, {0x2B5A, 0x2B73},
{0x2B76, 0x2B95}, {0x2B97, 0x2C2E}, {0x2C30, 0x2C5E},
{0x2C60, 0x2CF3}, {0x2CF9, 0x2D25}, {0x2D27, 0x2D27},
{0x2D2D, 0x2D2D}, {0x2D30, 0x2D67}, {0x2D6F, 0x2D70},
{0x2D7F, 0x2D96}, {0x2DA0, 0x2DA6}, {0x2DA8, 0x2DAE},
{0x2DB0, 0x2DB6}, {0x2DB8, 0x2DBE}, {0x2DC0, 0x2DC6},
{0x2DC8, 0x2DCE}, {0x2DD0, 0x2DD6}, {0x2DD8, 0x2DDE},
{0x2DE0, 0x2E52}, {0x303F, 0x303F}, {0x4DC0, 0x4DFF},
{0xA4D0, 0xA62B}, {0xA640, 0xA6F7}, {0xA700, 0xA7BF},
{0xA7C2, 0xA7CA}, {0xA7F5, 0xA82C}, {0xA830, 0xA839},
{0xA840, 0xA877}, {0xA880, 0xA8C5}, {0xA8CE, 0xA8D9},
{0xA8E0, 0xA953}, {0xA95F, 0xA95F}, {0xA980, 0xA9CD},
{0xA9CF, 0xA9D9}, {0xA9DE, 0xA9FE}, {0xAA00, 0xAA36},
{0xAA40, 0xAA4D}, {0xAA50, 0xAA59}, {0xAA5C, 0xAAC2},
{0xAADB, 0xAAF6}, {0xAB01, 0xAB06}, {0xAB09, 0xAB0E},
{0xAB11, 0xAB16}, {0xAB20, 0xAB26}, {0xAB28, 0xAB2E},
{0xAB30, 0xAB6B}, {0xAB70, 0xABED}, {0xABF0, 0xABF9},
{0xD7B0, 0xD7C6}, {0xD7CB, 0xD7FB}, {0xD800, 0xDFFF},
{0xFB00, 0xFB06}, {0xFB13, 0xFB17}, {0xFB1D, 0xFB36},
{0xFB38, 0xFB3C}, {0xFB3E, 0xFB3E}, {0xFB40, 0xFB41},
{0xFB43, 0xFB44}, {0xFB46, 0xFBC1}, {0xFBD3, 0xFD3F},
{0xFD50, 0xFD8F}, {0xFD92, 0xFDC7}, {0xFDF0, 0xFDFD},
{0xFE20, 0xFE2F}, {0xFE70, 0xFE74}, {0xFE76, 0xFEFC},
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFC}, {0x10000, 0x1000B},
{0x1000D, 0x10026}, {0x10028, 0x1003A}, {0x1003C, 0x1003D},
{0x1003F, 0x1004D}, {0x10050, 0x1005D}, {0x10080, 0x100FA},
{0x10100, 0x10102}, {0x10107, 0x10133}, {0x10137, 0x1018E},
{0x10190, 0x1019C}, {0x101A0, 0x101A0}, {0x101D0, 0x101FD},
{0x10280, 0x1029C}, {0x102A0, 0x102D0}, {0x102E0, 0x102FB},
{0x10300, 0x10323}, {0x1032D, 0x1034A}, {0x10350, 0x1037A},
{0x10380, 0x1039D}, {0x1039F, 0x103C3}, {0x103C8, 0x103D5},
{0x10400, 0x1049D}, {0x104A0, 0x104A9}, {0x104B0, 0x104D3},
{0x104D8, 0x104FB}, {0x10500, 0x10527}, {0x10530, 0x10563},
{0x1056F, 0x1056F}, {0x10600, 0x10736}, {0x10740, 0x10755},
{0x10760, 0x10767}, {0x10800, 0x10805}, {0x10808, 0x10808},
{0x1080A, 0x10835}, {0x10837, 0x10838}, {0x1083C, 0x1083C},
{0x1083F, 0x10855}, {0x10857, 0x1089E}, {0x108A7, 0x108AF},
{0x108E0, 0x108F2}, {0x108F4, 0x108F5}, {0x108FB, 0x1091B},
{0x1091F, 0x10939}, {0x1093F, 0x1093F}, {0x10980, 0x109B7},
{0x109BC, 0x109CF}, {0x109D2, 0x10A03}, {0x10A05, 0x10A06},
{0x10A0C, 0x10A13}, {0x10A15, 0x10A17}, {0x10A19, 0x10A35},
{0x10A38, 0x10A3A}, {0x10A3F, 0x10A48}, {0x10A50, 0x10A58},
{0x10A60, 0x10A9F}, {0x10AC0, 0x10AE6}, {0x10AEB, 0x10AF6},
{0x10B00, 0x10B35}, {0x10B39, 0x10B55}, {0x10B58, 0x10B72},
{0x10B78, 0x10B91}, {0x10B99, 0x10B9C}, {0x10BA9, 0x10BAF},
{0x10C00, 0x10C48}, {0x10C80, 0x10CB2}, {0x10CC0, 0x10CF2},
{0x10CFA, 0x10D27}, {0x10D30, 0x10D39}, {0x10E60, 0x10E7E},
{0x10E80, 0x10EA9}, {0x10EAB, 0x10EAD}, {0x10EB0, 0x10EB1},
{0x10F00, 0x10F27}, {0x10F30, 0x10F59}, {0x10FB0, 0x10FCB},
{0x10FE0, 0x10FF6}, {0x11000, 0x1104D}, {0x11052, 0x1106F},
{0x1107F, 0x110C1}, {0x110CD, 0x110CD}, {0x110D0, 0x110E8},
{0x110F0, 0x110F9}, {0x11100, 0x11134}, {0x11136, 0x11147},
{0x11150, 0x11176}, {0x11180, 0x111DF}, {0x111E1, 0x111F4},
{0x11200, 0x11211}, {0x11213, 0x1123E}, {0x11280, 0x11286},
{0x11288, 0x11288}, {0x1128A, 0x1128D}, {0x1128F, 0x1129D},
{0x1129F, 0x112A9}, {0x112B0, 0x112EA}, {0x112F0, 0x112F9},
{0x11300, 0x11303}, {0x11305, 0x1130C}, {0x1130F, 0x11310},
{0x11313, 0x11328}, {0x1132A, 0x11330}, {0x11332, 0x11333},
{0x11335, 0x11339}, {0x1133B, 0x11344}, {0x11347, 0x11348},
{0x1134B, 0x1134D}, {0x11350, 0x11350}, {0x11357, 0x11357},
{0x1135D, 0x11363}, {0x11366, 0x1136C}, {0x11370, 0x11374},
{0x11400, 0x1145B}, {0x1145D, 0x11461}, {0x11480, 0x114C7},
{0x114D0, 0x114D9}, {0x11580, 0x115B5}, {0x115B8, 0x115DD},
{0x11600, 0x11644}, {0x11650, 0x11659}, {0x11660, 0x1166C},
{0x11680, 0x116B8}, {0x116C0, 0x116C9}, {0x11700, 0x1171A},
{0x1171D, 0x1172B}, {0x11730, 0x1173F}, {0x11800, 0x1183B},
{0x118A0, 0x118F2}, {0x118FF, 0x11906}, {0x11909, 0x11909},
{0x1190C, 0x11913}, {0x11915, 0x11916}, {0x11918, 0x11935},
{0x11937, 0x11938}, {0x1193B, 0x11946}, {0x11950, 0x11959},
{0x119A0, 0x119A7}, {0x119AA, 0x119D7}, {0x119DA, 0x119E4},
{0x11A00, 0x11A47}, {0x11A50, 0x11AA2}, {0x11AC0, 0x11AF8},
{0x11C00, 0x11C08}, {0x11C0A, 0x11C36}, {0x11C38, 0x11C45},
{0x11C50, 0x11C6C}, {0x11C70, 0x11C8F}, {0x11C92, 0x11CA7},
{0x11CA9, 0x11CB6}, {0x11D00, 0x11D06}, {0x11D08, 0x11D09},
{0x11D0B, 0x11D36}, {0x11D3A, 0x11D3A}, {0x11D3C, 0x11D3D},
{0x11D3F, 0x11D47}, {0x11D50, 0x11D59}, {0x11D60, 0x11D65},
{0x11D67, 0x11D68}, {0x11D6A, 0x11D8E}, {0x11D90, 0x11D91},
{0x11D93, 0x11D98}, {0x11DA0, 0x11DA9}, {0x11EE0, 0x11EF8},
{0x11FB0, 0x11FB0}, {0x11FC0, 0x11FF1}, {0x11FFF, 0x12399},
{0x12400, 0x1246E}, {0x12470, 0x12474}, {0x12480, 0x12543},
{0x13000, 0x1342E}, {0x13430, 0x13438}, {0x14400, 0x14646},
{0x16800, 0x16A38}, {0x16A40, 0x16A5E}, {0x16A60, 0x16A69},
{0x16A6E, 0x16A6F}, {0x16AD0, 0x16AED}, {0x16AF0, 0x16AF5},
{0x16B00, 0x16B45}, {0x16B50, 0x16B59}, {0x16B5B, 0x16B61},
{0x16B63, 0x16B77}, {0x16B7D, 0x16B8F}, {0x16E40, 0x16E9A},
{0x16F00, 0x16F4A}, {0x16F4F, 0x16F87}, {0x16F8F, 0x16F9F},
{0x1BC00, 0x1BC6A}, {0x1BC70, 0x1BC7C}, {0x1BC80, 0x1BC88},
{0x1BC90, 0x1BC99}, {0x1BC9C, 0x1BCA3}, {0x1D000, 0x1D0F5},
{0x1D100, 0x1D126}, {0x1D129, 0x1D1E8}, {0x1D200, 0x1D245},
{0x1D2E0, 0x1D2F3}, {0x1D300, 0x1D356}, {0x1D360, 0x1D378},
{0x1D400, 0x1D454}, {0x1D456, 0x1D49C}, {0x1D49E, 0x1D49F},
{0x1D4A2, 0x1D4A2}, {0x1D4A5, 0x1D4A6}, {0x1D4A9, 0x1D4AC},
{0x1D4AE, 0x1D4B9}, {0x1D4BB, 0x1D4BB}, {0x1D4BD, 0x1D4C3},
{0x1D4C5, 0x1D505}, {0x1D507, 0x1D50A}, {0x1D50D, 0x1D514},
{0x1D516, 0x1D51C}, {0x1D51E, 0x1D539}, {0x1D53B, 0x1D53E},
{0x1D540, 0x1D544}, {0x1D546, 0x1D546}, {0x1D54A, 0x1D550},
{0x1D552, 0x1D6A5}, {0x1D6A8, 0x1D7CB}, {0x1D7CE, 0x1DA8B},
{0x1DA9B, 0x1DA9F}, {0x1DAA1, 0x1DAAF}, {0x1E000, 0x1E006},
{0x1E008, 0x1E018}, {0x1E01B, 0x1E021}, {0x1E023, 0x1E024},
{0x1E026, 0x1E02A}, {0x1E100, 0x1E12C}, {0x1E130, 0x1E13D},
{0x1E140, 0x1E149}, {0x1E14E, 0x1E14F}, {0x1E2C0, 0x1E2F9},
{0x1E2FF, 0x1E2FF}, {0x1E800, 0x1E8C4}, {0x1E8C7, 0x1E8D6},
{0x1E900, 0x1E94B}, {0x1E950, 0x1E959}, {0x1E95E, 0x1E95F},
{0x1EC71, 0x1ECB4}, {0x1ED01, 0x1ED3D}, {0x1EE00, 0x1EE03},
{0x1EE05, 0x1EE1F}, {0x1EE21, 0x1EE22}, {0x1EE24, 0x1EE24},
{0x1EE27, 0x1EE27}, {0x1EE29, 0x1EE32}, {0x1EE34, 0x1EE37},
{0x1EE39, 0x1EE39}, {0x1EE3B, 0x1EE3B}, {0x1EE42, 0x1EE42},
{0x1EE47, 0x1EE47}, {0x1EE49, 0x1EE49}, {0x1EE4B, 0x1EE4B},
{0x1EE4D, 0x1EE4F}, {0x1EE51, 0x1EE52}, {0x1EE54, 0x1EE54},
{0x1EE57, 0x1EE57}, {0x1EE59, 0x1EE59}, {0x1EE5B, 0x1EE5B},
{0x1EE5D, 0x1EE5D}, {0x1EE5F, 0x1EE5F}, {0x1EE61, 0x1EE62},
{0x1EE64, 0x1EE64}, {0x1EE67, 0x1EE6A}, {0x1EE6C, 0x1EE72},
{0x1EE74, 0x1EE77}, {0x1EE79, 0x1EE7C}, {0x1EE7E, 0x1EE7E},
{0x1EE80, 0x1EE89}, {0x1EE8B, 0x1EE9B}, {0x1EEA1, 0x1EEA3},
{0x1EEA5, 0x1EEA9}, {0x1EEAB, 0x1EEBB}, {0x1EEF0, 0x1EEF1},
{0x1F000, 0x1F003}, {0x1F005, 0x1F02B}, {0x1F030, 0x1F093},
{0x1F0A0, 0x1F0AE}, {0x1F0B1, 0x1F0BF}, {0x1F0C1, 0x1F0CE},
{0x1F0D1, 0x1F0F5}, {0x1F10B, 0x1F10F}, {0x1F12E, 0x1F12F},
{0x1F16A, 0x1F16F}, {0x1F1AD, 0x1F1AD}, {0x1F1E6, 0x1F1FF},
{0x1F321, 0x1F32C}, {0x1F336, 0x1F336}, {0x1F37D, 0x1F37D},
{0x1F394, 0x1F39F}, {0x1F3CB, 0x1F3CE}, {0x1F3D4, 0x1F3DF},
{0x1F3F1, 0x1F3F3}, {0x1F3F5, 0x1F3F7}, {0x1F43F, 0x1F43F},
{0x1F441, 0x1F441}, {0x1F4FD, 0x1F4FE}, {0x1F53E, 0x1F54A},
{0x1F54F, 0x1F54F}, {0x1F568, 0x1F579}, {0x1F57B, 0x1F594},
{0x1F597, 0x1F5A3}, {0x1F5A5, 0x1F5FA}, {0x1F650, 0x1F67F},
{0x1F6C6, 0x1F6CB}, {0x1F6CD, 0x1F6CF}, {0x1F6D3, 0x1F6D4},
{0x1F6E0, 0x1F6EA}, {0x1F6F0, 0x1F6F3}, {0x1F700, 0x1F773},
{0x1F780, 0x1F7D8}, {0x1F800, 0x1F80B}, {0x1F810, 0x1F847},
{0x1F850, 0x1F859}, {0x1F860, 0x1F887}, {0x1F890, 0x1F8AD},
{0x1F8B0, 0x1F8B1}, {0x1F900, 0x1F90B}, {0x1F93B, 0x1F93B},
{0x1F946, 0x1F946}, {0x1FA00, 0x1FA53}, {0x1FA60, 0x1FA6D},
{0x1FB00, 0x1FB92}, {0x1FB94, 0x1FBCA}, {0x1FBF0, 0x1FBF9},
{0xE0001, 0xE0001}, {0xE0020, 0xE007F},
}
var emoji = table{
{0x203C, 0x203C}, {0x2049, 0x2049}, {0x2122, 0x2122},
{0x2139, 0x2139}, {0x2194, 0x2199}, {0x21A9, 0x21AA},
{0x231A, 0x231B}, {0x2328, 0x2328}, {0x2388, 0x2388},
{0x23CF, 0x23CF}, {0x23E9, 0x23F3}, {0x23F8, 0x23FA},
{0x24C2, 0x24C2}, {0x25AA, 0x25AB}, {0x25B6, 0x25B6},
{0x25C0, 0x25C0}, {0x25FB, 0x25FE}, {0x2600, 0x2605},
{0x2607, 0x2612}, {0x2614, 0x2685}, {0x2690, 0x2705},
{0x2708, 0x2712}, {0x2714, 0x2714}, {0x2716, 0x2716},
{0x271D, 0x271D}, {0x2721, 0x2721}, {0x2728, 0x2728},
{0x2733, 0x2734}, {0x2744, 0x2744}, {0x2747, 0x2747},
{0x274C, 0x274C}, {0x274E, 0x274E}, {0x2753, 0x2755},
{0x2757, 0x2757}, {0x2763, 0x2767}, {0x2795, 0x2797},
{0x27A1, 0x27A1}, {0x27B0, 0x27B0}, {0x27BF, 0x27BF},
{0x2934, 0x2935}, {0x2B05, 0x2B07}, {0x2B1B, 0x2B1C},
{0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x3030, 0x3030},
{0x303D, 0x303D}, {0x3297, 0x3297}, {0x3299, 0x3299},
{0x1F000, 0x1F0FF}, {0x1F10D, 0x1F10F}, {0x1F12F, 0x1F12F},
{0x1F16C, 0x1F171}, {0x1F17E, 0x1F17F}, {0x1F18E, 0x1F18E},
{0x1F191, 0x1F19A}, {0x1F1AD, 0x1F1E5}, {0x1F201, 0x1F20F},
{0x1F21A, 0x1F21A}, {0x1F22F, 0x1F22F}, {0x1F232, 0x1F23A},
{0x1F23C, 0x1F23F}, {0x1F249, 0x1F3FA}, {0x1F400, 0x1F53D},
{0x1F546, 0x1F64F}, {0x1F680, 0x1F6FF}, {0x1F774, 0x1F77F},
{0x1F7D5, 0x1F7FF}, {0x1F80C, 0x1F80F}, {0x1F848, 0x1F84F},
{0x1F85A, 0x1F85F}, {0x1F888, 0x1F88F}, {0x1F8AE, 0x1F8FF},
{0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1FAFF},
{0x1FC00, 0x1FFFD},
}

View File

@ -0,0 +1,28 @@
// +build windows
// +build !appengine
package runewidth
import (
"syscall"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32")
procGetConsoleOutputCP = kernel32.NewProc("GetConsoleOutputCP")
)
// IsEastAsian return true if the current locale is CJK
func IsEastAsian() bool {
r1, _, _ := procGetConsoleOutputCP.Call()
if r1 == 0 {
return false
}
switch int(r1) {
case 932, 51932, 936, 949, 950:
return true
}
return false
}

View File

@ -6,28 +6,15 @@ import (
"fmt"
"io"
"log"
"runtime/debug"
"strings"
"time"
"unicode/utf8"
"github.com/acarl005/stripansi"
"github.com/mattn/go-runewidth"
"github.com/vbauerster/mpb/v5/decor"
)
// BarFiller interface.
// Bar renders itself by calling BarFiller's Fill method. You can
// literally have any bar kind, by implementing this interface and
// passing it to the *Progress.Add(...) *Bar method.
type BarFiller interface {
Fill(w io.Writer, width int, stat *decor.Statistics)
}
// BarFillerFunc is function type adapter to convert function into Filler.
type BarFillerFunc func(w io.Writer, width int, stat *decor.Statistics)
func (f BarFillerFunc) Fill(w io.Writer, width int, stat *decor.Statistics) {
f(w, width, stat)
}
// Bar represents a progress Bar.
type Bar struct {
priority int // used by heap
@ -55,21 +42,22 @@ type Bar struct {
recoveredPanic interface{}
}
type extFunc func(in io.Reader, tw int, st *decor.Statistics) (out io.Reader, lines int)
type extFunc func(in io.Reader, reqWidth int, st decor.Statistics) (out io.Reader, lines int)
type bState struct {
baseF BarFiller
filler BarFiller
id int
width int
priority int
reqWidth int
total int64
current int64
refill int64
lastN int64
iterated bool
trimSpace bool
toComplete bool
completeFlushed bool
ignoreComplete bool
dropOnComplete bool
noPop bool
aDecorators []decor.Decorator
pDecorators []decor.Decorator
@ -77,12 +65,10 @@ type bState struct {
ewmaDecorators []decor.EwmaDecorator
shutdownListeners []decor.ShutdownListener
bufP, bufB, bufA *bytes.Buffer
filler BarFiller
middleware func(BarFiller) BarFiller
extender extFunc
// priority overrides *Bar's priority, if set
priority int
// dropOnComplete propagates to *Bar
dropOnComplete bool
// runningBar is a key for *pState.parkedBars
runningBar *Bar
@ -146,13 +132,8 @@ func (b *Bar) Current() int64 {
// Given default bar style is "[=>-]<+", refill rune is '+'.
// To set bar style use mpb.BarStyle(string) BarOption.
func (b *Bar) SetRefill(amount int64) {
type refiller interface {
SetRefill(int64)
}
b.operateState <- func(s *bState) {
if f, ok := s.baseF.(refiller); ok {
f.SetRefill(amount)
}
s.refill = amount
}
}
@ -318,44 +299,40 @@ func (b *Bar) serve(ctx context.Context, s *bState) {
}
func (b *Bar) render(tw int) {
if b.recoveredPanic != nil {
b.toShutdown = false
b.frameCh <- b.panicToFrame(tw)
return
}
select {
case b.operateState <- func(s *bState) {
stat := newStatistics(tw, s)
defer func() {
// recovering if user defined decorator panics for example
if p := recover(); p != nil {
b.dlogger.Println(p)
s.extender = makePanicExtender(p)
frame, lines := s.extender(nil, s.reqWidth, stat)
b.extendedLines = lines
b.toShutdown = !b.toShutdown
b.recoveredPanic = p
b.toShutdown = !s.completeFlushed
b.frameCh <- b.panicToFrame(tw)
b.frameCh <- frame
b.dlogger.Println(p)
}
s.completeFlushed = s.toComplete
}()
st := newStatistics(s)
frame := s.draw(tw, st)
frame, b.extendedLines = s.extender(frame, tw, st)
frame, lines := s.extender(s.draw(stat), s.reqWidth, stat)
b.extendedLines = lines
b.toShutdown = s.toComplete && !s.completeFlushed
s.completeFlushed = s.toComplete
b.frameCh <- frame
}:
case <-b.done:
s := b.cacheState
st := newStatistics(s)
frame := s.draw(tw, st)
frame, b.extendedLines = s.extender(frame, tw, st)
stat := newStatistics(tw, s)
var r io.Reader
if b.recoveredPanic == nil {
r = s.draw(stat)
}
frame, lines := s.extender(r, s.reqWidth, stat)
b.extendedLines = lines
b.frameCh <- frame
}
}
func (b *Bar) panicToFrame(termWidth int) io.Reader {
return strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%dv\n", termWidth), b.recoveredPanic))
}
func (b *Bar) subscribeDecorators() {
var averageDecorators []decor.AverageDecorator
var ewmaDecorators []decor.EwmaDecorator
@ -398,34 +375,41 @@ func (b *Bar) wSyncTable() [][]chan int {
}
}
func (s *bState) draw(termWidth int, stat *decor.Statistics) io.Reader {
func (s *bState) draw(stat decor.Statistics) io.Reader {
if !s.trimSpace {
stat.AvailableWidth -= 2
s.bufB.WriteByte(' ')
defer s.bufB.WriteByte(' ')
}
nlr := strings.NewReader("\n")
tw := stat.AvailableWidth
for _, d := range s.pDecorators {
s.bufP.WriteString(d.Decor(stat))
str := d.Decor(stat)
stat.AvailableWidth -= runewidth.StringWidth(stripansi.Strip(str))
s.bufP.WriteString(str)
}
if stat.AvailableWidth <= 0 {
trunc := strings.NewReader(runewidth.Truncate(stripansi.Strip(s.bufP.String()), tw, "…"))
s.bufP.Reset()
return io.MultiReader(trunc, s.bufB, nlr)
}
tw = stat.AvailableWidth
for _, d := range s.aDecorators {
s.bufA.WriteString(d.Decor(stat))
str := d.Decor(stat)
stat.AvailableWidth -= runewidth.StringWidth(stripansi.Strip(str))
s.bufA.WriteString(str)
}
if stat.AvailableWidth <= 0 {
trunc := strings.NewReader(runewidth.Truncate(stripansi.Strip(s.bufA.String()), tw, "…"))
s.bufA.Reset()
return io.MultiReader(s.bufP, s.bufB, trunc, nlr)
}
s.bufA.WriteByte('\n')
s.filler.Fill(s.bufB, s.reqWidth, stat)
prependCount := utf8.RuneCount(s.bufP.Bytes())
appendCount := utf8.RuneCount(s.bufA.Bytes()) - 1
if fitWidth := s.width; termWidth > 1 {
if !s.trimSpace {
// reserve space for edge spaces
termWidth -= 2
s.bufB.WriteByte(' ')
defer s.bufB.WriteByte(' ')
}
if prependCount+s.width+appendCount > termWidth {
fitWidth = termWidth - prependCount - appendCount
}
s.filler.Fill(s.bufB, fitWidth, stat)
}
return io.MultiReader(s.bufP, s.bufB, s.bufA)
return io.MultiReader(s.bufP, s.bufB, s.bufA, nlr)
}
func (s *bState) wSyncTable() [][]chan int {
@ -450,12 +434,14 @@ func (s *bState) wSyncTable() [][]chan int {
return table
}
func newStatistics(s *bState) *decor.Statistics {
return &decor.Statistics{
ID: s.id,
Completed: s.completeFlushed,
Total: s.total,
Current: s.current,
func newStatistics(tw int, s *bState) decor.Statistics {
return decor.Statistics{
ID: s.id,
AvailableWidth: tw,
Total: s.total,
Current: s.current,
Refill: s.refill,
Completed: s.completeFlushed,
}
}
@ -476,3 +462,17 @@ func ewmaIterationUpdate(done bool, s *bState, dur time.Duration) {
d.EwmaUpdate(s.lastN, dur)
}
}
func makePanicExtender(p interface{}) extFunc {
pstr := fmt.Sprint(p)
stack := debug.Stack()
stackLines := bytes.Count(stack, []byte("\n"))
return func(_ io.Reader, _ int, st decor.Statistics) (io.Reader, int) {
mr := io.MultiReader(
strings.NewReader(runewidth.Truncate(pstr, st.AvailableWidth, "…")),
strings.NewReader(fmt.Sprintf("\n%#v\n", st)),
bytes.NewReader(stack),
)
return mr, stackLines + 1
}
}

View File

@ -2,137 +2,29 @@ package mpb
import (
"io"
"unicode/utf8"
"github.com/vbauerster/mpb/v5/decor"
"github.com/vbauerster/mpb/v5/internal"
)
const (
rLeft = iota
rFill
rTip
rEmpty
rRight
rRevTip
rRefill
)
// DefaultBarStyle is a string containing 7 runes.
// Each rune is a building block of a progress bar.
// BarFiller interface.
// Bar (without decorators) renders itself by calling BarFiller's Fill method.
//
// '1st rune' stands for left boundary rune
// `reqWidth` is requested width, which is set via:
// func WithWidth(width int) ContainerOption
// func BarWidth(width int) BarOption
//
// '2nd rune' stands for fill rune
// Default implementations can be obtained via:
//
// '3rd rune' stands for tip rune
// func NewBarFiller(style string, reverse bool) BarFiller
// func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller
//
// '4th rune' stands for empty rune
//
// '5th rune' stands for right boundary rune
//
// '6th rune' stands for reverse tip rune
//
// '7th rune' stands for refill rune
//
const DefaultBarStyle string = "[=>-]<+"
type barFiller struct {
format [][]byte
tip []byte
refill int64
reverse bool
flush func(w io.Writer, bb [][]byte)
type BarFiller interface {
Fill(w io.Writer, reqWidth int, stat decor.Statistics)
}
// NewBarFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method.
func NewBarFiller(style string, reverse bool) BarFiller {
if style == "" {
style = DefaultBarStyle
}
bf := &barFiller{
format: make([][]byte, utf8.RuneCountInString(style)),
reverse: reverse,
}
bf.SetStyle(style)
return bf
}
func (s *barFiller) SetStyle(style string) {
if !utf8.ValidString(style) {
return
}
src := make([][]byte, 0, utf8.RuneCountInString(style))
for _, r := range style {
src = append(src, []byte(string(r)))
}
copy(s.format, src)
s.SetReverse(s.reverse)
}
func (s *barFiller) SetReverse(reverse bool) {
if reverse {
s.tip = s.format[rRevTip]
s.flush = reverseFlush
} else {
s.tip = s.format[rTip]
s.flush = regularFlush
}
s.reverse = reverse
}
func (s *barFiller) SetRefill(amount int64) {
s.refill = amount
}
func (s *barFiller) Fill(w io.Writer, width int, stat *decor.Statistics) {
// don't count rLeft and rRight as progress
width -= 2
if width < 2 {
return
}
w.Write(s.format[rLeft])
defer w.Write(s.format[rRight])
bb := make([][]byte, width)
cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width))
for i := 0; i < cwidth; i++ {
bb[i] = s.format[rFill]
}
if s.refill > 0 {
var rwidth int
if s.refill > stat.Current {
rwidth = cwidth
} else {
rwidth = int(internal.PercentageRound(stat.Total, int64(s.refill), width))
}
for i := 0; i < rwidth; i++ {
bb[i] = s.format[rRefill]
}
}
if cwidth > 0 && cwidth < width {
bb[cwidth-1] = s.tip
}
for i := cwidth; i < width; i++ {
bb[i] = s.format[rEmpty]
}
s.flush(w, bb)
}
func regularFlush(w io.Writer, bb [][]byte) {
for i := 0; i < len(bb); i++ {
w.Write(bb[i])
}
}
func reverseFlush(w io.Writer, bb [][]byte) {
for i := len(bb) - 1; i >= 0; i-- {
w.Write(bb[i])
}
// BarFillerFunc is function type adapter to convert function into BarFiller.
type BarFillerFunc func(w io.Writer, reqWidth int, stat decor.Statistics)
func (f BarFillerFunc) Fill(w io.Writer, reqWidth int, stat decor.Statistics) {
f(w, reqWidth, stat)
}

173
vendor/github.com/vbauerster/mpb/v5/bar_filler_bar.go generated vendored Normal file
View File

@ -0,0 +1,173 @@
package mpb
import (
"bytes"
"io"
"unicode/utf8"
"github.com/mattn/go-runewidth"
"github.com/vbauerster/mpb/v5/decor"
"github.com/vbauerster/mpb/v5/internal"
)
const (
rLeft = iota
rFill
rTip
rSpace
rRight
rRevTip
rRefill
)
// DefaultBarStyle is a string containing 7 runes.
// Each rune is a building block of a progress bar.
//
// '1st rune' stands for left boundary rune
//
// '2nd rune' stands for fill rune
//
// '3rd rune' stands for tip rune
//
// '4th rune' stands for space rune
//
// '5th rune' stands for right boundary rune
//
// '6th rune' stands for reverse tip rune
//
// '7th rune' stands for refill rune
//
const DefaultBarStyle string = "[=>-]<+"
type barFiller struct {
format [][]byte
rwidth []int
tip []byte
refill int64
reverse bool
flush func(io.Writer, *space, [][]byte)
}
type space struct {
space []byte
rwidth int
count int
}
// NewBarFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method.
func NewBarFiller(style string, reverse bool) BarFiller {
bf := &barFiller{
format: make([][]byte, len(DefaultBarStyle)),
rwidth: make([]int, len(DefaultBarStyle)),
reverse: reverse,
}
bf.SetStyle(style)
return bf
}
func (s *barFiller) SetStyle(style string) {
if !utf8.ValidString(style) {
panic("invalid bar style")
}
if style == "" {
style = DefaultBarStyle
}
src := make([][]byte, utf8.RuneCountInString(style))
i := 0
for _, r := range style {
s.rwidth[i] = runewidth.RuneWidth(r)
src[i] = []byte(string(r))
i++
}
copy(s.format, src)
s.SetReverse(s.reverse)
}
func (s *barFiller) SetReverse(reverse bool) {
if reverse {
s.tip = s.format[rRevTip]
s.flush = reverseFlush
} else {
s.tip = s.format[rTip]
s.flush = regularFlush
}
s.reverse = reverse
}
func (s *barFiller) Fill(w io.Writer, reqWidth int, stat decor.Statistics) {
width := internal.WidthForBarFiller(reqWidth, stat.AvailableWidth)
if brackets := s.rwidth[rLeft] + s.rwidth[rRight]; width < brackets {
return
} else {
// don't count brackets as progress
width -= brackets
}
w.Write(s.format[rLeft])
defer w.Write(s.format[rRight])
cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width))
space := &space{
space: s.format[rSpace],
rwidth: s.rwidth[rSpace],
count: width - cwidth,
}
index, refill := 0, 0
bb := make([][]byte, cwidth)
if cwidth > 0 && cwidth != width {
bb[index] = s.tip
cwidth -= s.rwidth[rTip]
index++
}
if stat.Refill > 0 {
refill = int(internal.PercentageRound(stat.Total, int64(stat.Refill), width))
if refill > cwidth {
refill = cwidth
}
cwidth -= refill
}
for cwidth > 0 {
bb[index] = s.format[rFill]
cwidth -= s.rwidth[rFill]
index++
}
for refill > 0 {
bb[index] = s.format[rRefill]
refill -= s.rwidth[rRefill]
index++
}
if cwidth+refill < 0 || space.rwidth > 1 {
buf := new(bytes.Buffer)
s.flush(buf, space, bb[:index])
io.WriteString(w, runewidth.Truncate(buf.String(), width, "…"))
return
}
s.flush(w, space, bb)
}
func regularFlush(w io.Writer, space *space, bb [][]byte) {
for i := len(bb) - 1; i >= 0; i-- {
w.Write(bb[i])
}
for space.count > 0 {
w.Write(space.space)
space.count -= space.rwidth
}
}
func reverseFlush(w io.Writer, space *space, bb [][]byte) {
for space.count > 0 {
w.Write(space.space)
space.count -= space.rwidth
}
for i := 0; i < len(bb); i++ {
w.Write(bb[i])
}
}

View File

@ -6,6 +6,7 @@ import (
"unicode/utf8"
"github.com/vbauerster/mpb/v5/decor"
"github.com/vbauerster/mpb/v5/internal"
)
// SpinnerAlignment enum.
@ -39,7 +40,8 @@ func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller {
return filler
}
func (s *spinnerFiller) Fill(w io.Writer, width int, stat *decor.Statistics) {
func (s *spinnerFiller) Fill(w io.Writer, reqWidth int, stat decor.Statistics) {
width := internal.WidthForBarFiller(reqWidth, stat.AvailableWidth)
frame := s.frames[s.count%uint(len(s.frames))]
frameWidth := utf8.RuneCountInString(frame)

View File

@ -46,7 +46,7 @@ func BarID(id int) BarOption {
// BarWidth sets bar width independent of the container.
func BarWidth(width int) BarOption {
return func(s *bState) {
s.width = width
s.reqWidth = width
}
}
@ -77,19 +77,22 @@ func BarFillerClearOnComplete() BarOption {
// BarFillerOnComplete replaces bar's filler with message, on complete event.
func BarFillerOnComplete(message string) BarOption {
return func(s *bState) {
s.filler = makeBarFillerOnComplete(s.baseF, message)
}
return BarFillerMiddleware(func(base BarFiller) BarFiller {
return BarFillerFunc(func(w io.Writer, reqWidth int, st decor.Statistics) {
if st.Completed {
io.WriteString(w, message)
} else {
base.Fill(w, reqWidth, st)
}
})
})
}
func makeBarFillerOnComplete(filler BarFiller, message string) BarFiller {
return BarFillerFunc(func(w io.Writer, width int, st *decor.Statistics) {
if st.Completed {
io.WriteString(w, message)
} else {
filler.Fill(w, width, st)
}
})
// BarFillerMiddleware provides a way to augment default BarFiller.
func BarFillerMiddleware(middle func(BarFiller) BarFiller) BarOption {
return func(s *bState) {
s.middleware = middle
}
}
// BarPriority sets bar's priority. Zero is highest priority, i.e. bar
@ -103,21 +106,20 @@ func BarPriority(priority int) BarOption {
// BarExtender is an option to extend bar to the next new line, with
// arbitrary output.
func BarExtender(extender BarFiller) BarOption {
if extender == nil {
func BarExtender(filler BarFiller) BarOption {
if filler == nil {
return nil
}
return func(s *bState) {
s.extender = makeExtFunc(extender)
s.extender = makeExtFunc(filler)
}
}
func makeExtFunc(extender BarFiller) extFunc {
func makeExtFunc(filler BarFiller) extFunc {
buf := new(bytes.Buffer)
nl := []byte("\n")
return func(r io.Reader, tw int, st *decor.Statistics) (io.Reader, int) {
extender.Fill(buf, tw, st)
return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), nl)
return func(r io.Reader, reqWidth int, st decor.Statistics) (io.Reader, int) {
filler.Fill(buf, reqWidth, st)
return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), []byte("\n"))
}
}
@ -139,7 +141,7 @@ func BarStyle(style string) BarOption {
SetStyle(string)
}
return func(s *bState) {
if t, ok := s.baseF.(styleSetter); ok {
if t, ok := s.filler.(styleSetter); ok {
t.SetStyle(style)
}
}
@ -159,7 +161,7 @@ func BarReverse() BarOption {
SetReverse(bool)
}
return func(s *bState) {
if t, ok := s.baseF.(revSetter); ok {
if t, ok := s.filler.(revSetter); ok {
t.SetReverse(true)
}
}
@ -189,7 +191,7 @@ func MakeFillerTypeSpecificBarOption(
cb func(interface{}),
) BarOption {
return func(s *bState) {
if t, ok := typeChecker(s.baseF); ok {
if t, ok := typeChecker(s.filler); ok {
cb(t)
}
}

View File

@ -21,14 +21,11 @@ func WithWaitGroup(wg *sync.WaitGroup) ContainerOption {
}
}
// WithWidth sets container width. Default is 80. Bars inherit this
// width, as long as no BarWidth is applied.
func WithWidth(w int) ContainerOption {
// WithWidth sets container width. If not set underlying bars will
// occupy whole term width.
func WithWidth(width int) ContainerOption {
return func(s *pState) {
if w < 0 {
return
}
s.width = w
s.reqWidth = width
}
}

View File

@ -7,7 +7,7 @@ import (
"io"
"os"
"golang.org/x/crypto/ssh/terminal"
"github.com/mattn/go-isatty"
)
// NotATTY not a TeleTYpewriter error.
@ -30,13 +30,14 @@ func New(out io.Writer) *Writer {
w := &Writer{out: out}
if f, ok := out.(*os.File); ok {
w.fd = f.Fd()
w.isTerminal = terminal.IsTerminal(int(w.fd))
w.isTerminal = isatty.IsTerminal(w.fd)
}
return w
}
// Flush flushes the underlying buffer.
func (w *Writer) Flush(lineCount int) (err error) {
// some terminals interpret clear 0 lines as clear 1
if w.lineCount > 0 {
w.clearLines()
}
@ -63,9 +64,9 @@ func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
// GetWidth returns width of underlying terminal.
func (w *Writer) GetWidth() (int, error) {
if w.isTerminal {
tw, _, err := terminal.GetSize(int(w.fd))
return tw, err
if !w.isTerminal {
return -1, NotATTY
}
return -1, NotATTY
tw, _, err := GetSize(w.fd)
return tw, err
}

View File

@ -2,8 +2,21 @@
package cwriter
import "fmt"
import (
"fmt"
"golang.org/x/sys/unix"
)
func (w *Writer) clearLines() {
fmt.Fprintf(w.out, cuuAndEd, w.lineCount)
}
// GetSize returns the dimensions of the given terminal.
func GetSize(fd uintptr) (width, height int, err error) {
ws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
if err != nil {
return -1, -1, err
}
return int(ws.Col), int(ws.Row), nil
}

View File

@ -14,7 +14,6 @@ var (
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
)
type coord struct {
@ -41,8 +40,9 @@ func (w *Writer) clearLines() {
if !w.isTerminal {
fmt.Fprintf(w.out, cuuAndEd, w.lineCount)
}
var info consoleScreenBufferInfo
procGetConsoleScreenBufferInfo.Call(w.fd, uintptr(unsafe.Pointer(&info)))
info := new(consoleScreenBufferInfo)
procGetConsoleScreenBufferInfo.Call(w.fd, uintptr(unsafe.Pointer(info)))
info.cursorPosition.y -= int16(w.lineCount)
if info.cursorPosition.y < 0 {
@ -51,10 +51,19 @@ func (w *Writer) clearLines() {
procSetConsoleCursorPosition.Call(w.fd, uintptr(uint32(uint16(info.cursorPosition.y))<<16|uint32(uint16(info.cursorPosition.x))))
// clear the lines
cursor := coord{
cursor := &coord{
x: info.window.left,
y: info.cursorPosition.y,
}
count := uint32(info.size.x) * uint32(w.lineCount)
procFillConsoleOutputCharacter.Call(w.fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(new(uint32))))
procFillConsoleOutputCharacter.Call(w.fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(cursor)), uintptr(unsafe.Pointer(new(uint32))))
}
// GetSize returns the visible dimensions of the given terminal.
//
// These dimensions don't include any scrollback buffer height.
func GetSize(fd uintptr) (width, height int, err error) {
info := new(consoleScreenBufferInfo)
procGetConsoleScreenBufferInfo.Call(fd, uintptr(unsafe.Pointer(info)))
return int(info.window.right - info.window.left), int(info.window.bottom - info.window.top), nil
}

View File

@ -1,21 +1,21 @@
package decor
// Any decorator displays text, that can be changed during decorator's
// lifetime via provided func call back.
// lifetime via provided DecorFunc.
//
// `f` call back which provides string to display
// `fn` DecorFunc callback
//
// `wcc` optional WC config
//
func Any(f func(*Statistics) string, wcc ...WC) Decorator {
return &any{initWC(wcc...), f}
func Any(fn DecorFunc, wcc ...WC) Decorator {
return &any{initWC(wcc...), fn}
}
type any struct {
WC
f func(*Statistics) string
fn DecorFunc
}
func (d *any) Decor(s *Statistics) string {
return d.FormatMsg(d.f(s))
func (d *any) Decor(s Statistics) string {
return d.FormatMsg(d.fn(s))
}

View File

@ -46,21 +46,21 @@ func Counters(unit int, pairFmt string, wcc ...WC) Decorator {
return Any(chooseSizeProducer(unit, pairFmt), wcc...)
}
func chooseSizeProducer(unit int, format string) func(*Statistics) string {
func chooseSizeProducer(unit int, format string) DecorFunc {
if format == "" {
format = "%d / %d"
}
switch unit {
case UnitKiB:
return func(s *Statistics) string {
return func(s Statistics) string {
return fmt.Sprintf(format, SizeB1024(s.Current), SizeB1024(s.Total))
}
case UnitKB:
return func(s *Statistics) string {
return func(s Statistics) string {
return fmt.Sprintf(format, SizeB1000(s.Current), SizeB1000(s.Total))
}
default:
return func(s *Statistics) string {
return func(s Statistics) string {
return fmt.Sprintf(format, s.Current, s.Total)
}
}

View File

@ -3,9 +3,9 @@ package decor
import (
"fmt"
"time"
"unicode/utf8"
"github.com/acarl005/stripansi"
"github.com/mattn/go-runewidth"
)
const (
@ -47,22 +47,32 @@ const (
// Statistics consists of progress related statistics, that Decorator
// may need.
type Statistics struct {
ID int
Completed bool
Total int64
Current int64
ID int
AvailableWidth int
Total int64
Current int64
Refill int64
Completed bool
}
// Decorator interface.
// Implementors should embed WC type, that way only single method
// Decor(*Statistics) needs to be implemented, the rest will be handled
// by WC type.
// Most of the time there is no need to implement this interface
// manually, as decor package already provides a wide range of decorators
// which implement this interface. If however built-in decorators don't
// meet your needs, you're free to implement your own one by implementing
// this particular interface. The easy way to go is to convert a
// `DecorFunc` into a `Decorator` interface by using provided
// `func Any(DecorFunc, ...WC) Decorator`.
type Decorator interface {
Configurator
Synchronizer
Decor(*Statistics) string
Decor(Statistics) string
}
// DecorFunc func type.
// To be used with `func Any`(DecorFunc, ...WC) Decorator`.
type DecorFunc func(Statistics) string
// Synchronizer interface.
// All decorators implement this interface implicitly. Its Sync
// method exposes width sync channel, if DSyncWidth bit is set.
@ -117,38 +127,35 @@ var (
// W represents width and C represents bit set of width related config.
// A decorator should embed WC, to enable width synchronization.
type WC struct {
W int
C int
dynFormat string
wsync chan int
W int
C int
fill func(s string, w int) string
wsync chan int
}
// FormatMsg formats final message according to WC.W and WC.C.
// Should be called by any Decorator implementation.
func (wc *WC) FormatMsg(msg string) string {
var format string
runeCount := utf8.RuneCountInString(stripansi.Strip(msg))
ansiCount := utf8.RuneCountInString(msg) - runeCount
pureWidth := runewidth.StringWidth(msg)
stripWidth := runewidth.StringWidth(stripansi.Strip(msg))
maxCell := wc.W
if (wc.C & DSyncWidth) != 0 {
cellCount := stripWidth
if (wc.C & DextraSpace) != 0 {
runeCount++
cellCount++
}
wc.wsync <- runeCount
max := <-wc.wsync
format = fmt.Sprintf(wc.dynFormat, ansiCount+max)
} else {
format = fmt.Sprintf(wc.dynFormat, ansiCount+wc.W)
wc.wsync <- cellCount
maxCell = <-wc.wsync
}
return fmt.Sprintf(format, msg)
return wc.fill(msg, maxCell+(pureWidth-stripWidth))
}
// Init initializes width related config.
func (wc *WC) Init() WC {
wc.dynFormat = "%%"
wc.fill = runewidth.FillLeft
if (wc.C & DidentRight) != 0 {
wc.dynFormat += "-"
wc.fill = runewidth.FillRight
}
wc.dynFormat += "%ds"
if (wc.C & DSyncWidth) != 0 {
// it's deliberate choice to override wsync on each Init() call,
// this way globals like WCSyncSpace can be reused

View File

@ -25,11 +25,11 @@ func Elapsed(style TimeStyle, wcc ...WC) Decorator {
func NewElapsed(style TimeStyle, startTime time.Time, wcc ...WC) Decorator {
var msg string
producer := chooseTimeProducer(style)
f := func(s *Statistics) string {
fn := func(s Statistics) string {
if !s.Completed {
msg = producer(time.Since(startTime))
}
return msg
}
return Any(f, wcc...)
return Any(fn, wcc...)
}

View File

@ -63,7 +63,7 @@ type movingAverageETA struct {
producer func(time.Duration) string
}
func (d *movingAverageETA) Decor(s *Statistics) string {
func (d *movingAverageETA) Decor(s Statistics) string {
v := math.Round(d.average.Value())
remaining := time.Duration((s.Total - s.Current) * int64(v))
if d.normalizer != nil {
@ -117,7 +117,7 @@ type averageETA struct {
producer func(time.Duration) string
}
func (d *averageETA) Decor(s *Statistics) string {
func (d *averageETA) Decor(s Statistics) string {
var remaining time.Duration
if s.Current != 0 {
durPerItem := float64(time.Since(d.startTime)) / float64(s.Current)

View File

@ -1,9 +1,10 @@
package decor
import (
"fmt"
"strings"
"unicode/utf8"
"github.com/acarl005/stripansi"
"github.com/mattn/go-runewidth"
)
// Merge wraps its decorator argument with intention to sync width
@ -64,18 +65,18 @@ func (d *mergeDecorator) Base() Decorator {
return d.Decorator
}
func (d *mergeDecorator) Decor(s *Statistics) string {
func (d *mergeDecorator) Decor(s Statistics) string {
msg := d.Decorator.Decor(s)
msgLen := utf8.RuneCountInString(msg)
pureWidth := runewidth.StringWidth(msg)
stripWidth := runewidth.StringWidth(stripansi.Strip(msg))
cellCount := stripWidth
if (d.wc.C & DextraSpace) != 0 {
msgLen++
cellCount++
}
var total int
max := utf8.RuneCountInString(d.placeHolders[0].FormatMsg(""))
total += max
pw := (msgLen - max) / len(d.placeHolders)
rem := (msgLen - max) % len(d.placeHolders)
total := runewidth.StringWidth(d.placeHolders[0].FormatMsg(""))
pw := (cellCount - total) / len(d.placeHolders)
rem := (cellCount - total) % len(d.placeHolders)
var diff int
for i := 1; i < len(d.placeHolders); i++ {
@ -87,20 +88,20 @@ func (d *mergeDecorator) Decor(s *Statistics) string {
width = 0
}
}
max = utf8.RuneCountInString(ph.FormatMsg(strings.Repeat(" ", width)))
max := runewidth.StringWidth(ph.FormatMsg(strings.Repeat(" ", width)))
total += max
diff = max - pw
}
d.wc.wsync <- pw + rem
max = <-d.wc.wsync
return fmt.Sprintf(fmt.Sprintf(d.wc.dynFormat, max+total), msg)
max := <-d.wc.wsync
return d.wc.fill(msg, max+total+(pureWidth-stripWidth))
}
type placeHolderDecorator struct {
WC
}
func (d *placeHolderDecorator) Decor(*Statistics) string {
func (d *placeHolderDecorator) Decor(Statistics) string {
return ""
}

View File

@ -8,5 +8,5 @@ package decor
// `wcc` optional WC config
//
func Name(str string, wcc ...WC) Decorator {
return Any(func(*Statistics) string { return str }, wcc...)
return Any(func(Statistics) string { return str }, wcc...)
}

View File

@ -24,7 +24,7 @@ type onCompleteWrapper struct {
msg string
}
func (d *onCompleteWrapper) Decor(s *Statistics) string {
func (d *onCompleteWrapper) Decor(s Statistics) string {
if s.Completed {
wc := d.GetConf()
return wc.FormatMsg(d.msg)

Some files were not shown because too many files have changed in this diff Show More