mirror of
https://github.com/containers/skopeo.git
synced 2025-07-06 03:16:55 +00:00
Merge pull request #1070 from rhatdan/format
Add --format option to skopeo inspect
This commit is contained in:
commit
353f3a23e1
@ -4,8 +4,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/containers/common/pkg/report"
|
||||||
"github.com/containers/common/pkg/retry"
|
"github.com/containers/common/pkg/retry"
|
||||||
"github.com/containers/image/v5/docker"
|
"github.com/containers/image/v5/docker"
|
||||||
"github.com/containers/image/v5/image"
|
"github.com/containers/image/v5/image"
|
||||||
@ -23,6 +27,7 @@ type inspectOptions struct {
|
|||||||
global *globalOptions
|
global *globalOptions
|
||||||
image *imageOptions
|
image *imageOptions
|
||||||
retryOpts *retry.RetryOptions
|
retryOpts *retry.RetryOptions
|
||||||
|
format string
|
||||||
raw bool // Output the raw manifest instead of parsing information about the image
|
raw bool // Output the raw manifest instead of parsing information about the image
|
||||||
config bool // Output the raw config blob instead of parsing information about the image
|
config bool // Output the raw config blob instead of parsing information about the image
|
||||||
}
|
}
|
||||||
@ -46,12 +51,15 @@ Supported transports:
|
|||||||
See skopeo(1) section "IMAGE NAMES" for the expected format
|
See skopeo(1) section "IMAGE NAMES" for the expected format
|
||||||
`, strings.Join(transports.ListNames(), ", ")),
|
`, strings.Join(transports.ListNames(), ", ")),
|
||||||
RunE: commandAction(opts.run),
|
RunE: commandAction(opts.run),
|
||||||
Example: `skopeo inspect docker://docker.io/fedora`,
|
Example: `skopeo inspect docker://registry.fedoraproject.org/fedora
|
||||||
|
skopeo inspect --config docker://docker.io/alpine
|
||||||
|
skopeo inspect --format "Name: {{.Name}} Digest: {{.Digest}}" docker://registry.access.redhat.com/ubi8`,
|
||||||
}
|
}
|
||||||
adjustUsage(cmd)
|
adjustUsage(cmd)
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVar(&opts.raw, "raw", false, "output raw manifest or configuration")
|
flags.BoolVar(&opts.raw, "raw", false, "output raw manifest or configuration")
|
||||||
flags.BoolVar(&opts.config, "config", false, "output configuration")
|
flags.BoolVar(&opts.config, "config", false, "output configuration")
|
||||||
|
flags.StringVarP(&opts.format, "format", "f", "", "Format the output to a Go template")
|
||||||
flags.AddFlagSet(&sharedFlags)
|
flags.AddFlagSet(&sharedFlags)
|
||||||
flags.AddFlagSet(&imageFlags)
|
flags.AddFlagSet(&imageFlags)
|
||||||
flags.AddFlagSet(&retryFlags)
|
flags.AddFlagSet(&retryFlags)
|
||||||
@ -63,6 +71,7 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
|||||||
rawManifest []byte
|
rawManifest []byte
|
||||||
src types.ImageSource
|
src types.ImageSource
|
||||||
imgInspect *types.ImageInspectInfo
|
imgInspect *types.ImageInspectInfo
|
||||||
|
data []interface{}
|
||||||
)
|
)
|
||||||
ctx, cancel := opts.global.commandTimeoutContext()
|
ctx, cancel := opts.global.commandTimeoutContext()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -70,6 +79,9 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
|||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return errors.New("Exactly one argument expected")
|
return errors.New("Exactly one argument expected")
|
||||||
}
|
}
|
||||||
|
if opts.raw && opts.format != "" {
|
||||||
|
return errors.New("raw output does not support format option")
|
||||||
|
}
|
||||||
imageName := args[0]
|
imageName := args[0]
|
||||||
|
|
||||||
if err := reexecIfNecessaryForImages(imageName); err != nil {
|
if err := reexecIfNecessaryForImages(imageName); err != nil {
|
||||||
@ -106,12 +118,13 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error writing manifest to standard output: %v", err)
|
return fmt.Errorf("Error writing manifest to standard output: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
img, err := image.FromUnparsedImage(ctx, sys, image.UnparsedInstance(src, nil))
|
img, err := image.FromUnparsedImage(ctx, sys, image.UnparsedInstance(src, nil))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error parsing manifest for image: %v", err)
|
return errors.Wrapf(err, "Error parsing manifest for image")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.config && opts.raw {
|
if opts.config && opts.raw {
|
||||||
@ -124,7 +137,7 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
|||||||
}
|
}
|
||||||
_, err = stdout.Write(configBlob)
|
_, err = stdout.Write(configBlob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error writing configuration blob to standard output: %v", err)
|
return errors.Wrapf(err, "Error writing configuration blob to standard output")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
} else if opts.config {
|
} else if opts.config {
|
||||||
@ -135,9 +148,19 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
|||||||
}, opts.retryOpts); err != nil {
|
}, opts.retryOpts); err != nil {
|
||||||
return errors.Wrapf(err, "Error reading OCI-formatted configuration data")
|
return errors.Wrapf(err, "Error reading OCI-formatted configuration data")
|
||||||
}
|
}
|
||||||
err = json.NewEncoder(stdout).Encode(config)
|
if report.IsJSON(opts.format) || opts.format == "" {
|
||||||
|
var out []byte
|
||||||
|
out, err = json.MarshalIndent(config, "", " ")
|
||||||
|
if err == nil {
|
||||||
|
fmt.Fprintf(stdout, "%s\n", string(out))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
row := "{{range . }}" + report.NormalizeFormat(opts.format) + "{{end}}"
|
||||||
|
data = append(data, config)
|
||||||
|
err = printTmpl(row, data)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error writing OCI-formatted configuration data to standard output: %v", err)
|
return errors.Wrapf(err, "Error writing OCI-formatted configuration data to standard output")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -164,7 +187,7 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
|||||||
}
|
}
|
||||||
outputData.Digest, err = manifest.Digest(rawManifest)
|
outputData.Digest, err = manifest.Digest(rawManifest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error computing manifest digest: %v", err)
|
return errors.Wrapf(err, "Error computing manifest digest")
|
||||||
}
|
}
|
||||||
if dockerRef := img.Reference().DockerReference(); dockerRef != nil {
|
if dockerRef := img.Reference().DockerReference(); dockerRef != nil {
|
||||||
outputData.Name = dockerRef.Name()
|
outputData.Name = dockerRef.Name()
|
||||||
@ -182,15 +205,35 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
|||||||
// In addition, AWS ECR rejects it with 403 (Forbidden) if the "ecr:ListImages"
|
// In addition, AWS ECR rejects it with 403 (Forbidden) if the "ecr:ListImages"
|
||||||
// action is not allowed.
|
// action is not allowed.
|
||||||
if !strings.Contains(err.Error(), "401") && !strings.Contains(err.Error(), "403") {
|
if !strings.Contains(err.Error(), "401") && !strings.Contains(err.Error(), "403") {
|
||||||
return fmt.Errorf("Error determining repository tags: %v", err)
|
return errors.Wrapf(err, "Error determining repository tags")
|
||||||
}
|
}
|
||||||
logrus.Warnf("Registry disallows tag list retrieval; skipping")
|
logrus.Warnf("Registry disallows tag list retrieval; skipping")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if report.IsJSON(opts.format) || opts.format == "" {
|
||||||
out, err := json.MarshalIndent(outputData, "", " ")
|
out, err := json.MarshalIndent(outputData, "", " ")
|
||||||
|
if err == nil {
|
||||||
|
fmt.Fprintf(stdout, "%s\n", string(out))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
row := "{{range . }}" + report.NormalizeFormat(opts.format) + "{{end}}"
|
||||||
|
data = append(data, outputData)
|
||||||
|
return printTmpl(row, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func inspectNormalize(row string) string {
|
||||||
|
r := strings.NewReplacer(
|
||||||
|
".ImageID", ".Image",
|
||||||
|
)
|
||||||
|
return r.Replace(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTmpl(row string, data []interface{}) error {
|
||||||
|
t, err := template.New("skopeo inspect").Parse(row)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(stdout, "%s\n", string(out))
|
w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
|
||||||
return nil
|
return t.Execute(w, data)
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,7 @@ _skopeo_inspect() {
|
|||||||
--authfile
|
--authfile
|
||||||
--creds
|
--creds
|
||||||
--cert-dir
|
--cert-dir
|
||||||
|
--format
|
||||||
--retry-times
|
--retry-times
|
||||||
--registry-token
|
--registry-token
|
||||||
"
|
"
|
||||||
|
@ -4,38 +4,58 @@
|
|||||||
skopeo\-inspect - Return low-level information about _image-name_ in a registry.
|
skopeo\-inspect - Return low-level information about _image-name_ in a registry.
|
||||||
|
|
||||||
## SYNOPSIS
|
## SYNOPSIS
|
||||||
**skopeo inspect** [**--raw**] [**--config**] _image-name_
|
**skopeo inspect** [*options*] _image-name_
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
|
||||||
Return low-level information about _image-name_ in a registry
|
Return low-level information about _image-name_ in a registry
|
||||||
|
|
||||||
**--raw** output raw manifest, default is to format in JSON
|
_image-name_ name of image to retrieve information about
|
||||||
|
|
||||||
_image-name_ name of image to retrieve information about
|
## OPTIONS
|
||||||
|
|
||||||
**--config** output configuration in OCI format, default is to format in JSON
|
**--authfile** _path_
|
||||||
|
|
||||||
_image-name_ name of image to retrieve configuration for
|
Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `skopeo login`.
|
||||||
|
If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`.
|
||||||
|
|
||||||
**--config** **--raw** output configuration in raw format
|
**--cert-dir** _path_
|
||||||
|
|
||||||
_image-name_ name of image to retrieve configuration for
|
Use certificates at _path_ (\*.crt, \*.cert, \*.key) to connect to the registry.
|
||||||
|
|
||||||
**--authfile** _path_
|
**--config**
|
||||||
|
|
||||||
Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `skopeo login`.
|
Output configuration in OCI format, default is to format in JSON format.
|
||||||
If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`.
|
|
||||||
|
|
||||||
**--creds** _username[:password]_ for accessing the registry.
|
**--creds** _username[:password]_
|
||||||
|
|
||||||
**--cert-dir** _path_ Use certificates at _path_ (\*.crt, \*.cert, \*.key) to connect to the registry.
|
Username and password for accessing the registry.
|
||||||
|
|
||||||
**--retry-times** the number of times to retry, retry wait time will be exponentially increased based on the number of failed attempts.
|
**--format**, **-f**=*format*
|
||||||
|
|
||||||
**--tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container registries (defaults to true).
|
Format the output using the given Go template.
|
||||||
|
The keys of the returned JSON can be used as the values for the --format flag (see examples below).
|
||||||
|
|
||||||
**--no-creds** _bool-value_ Access the registry anonymously.
|
**--no-creds**
|
||||||
|
|
||||||
**--registry-token** _Bearer token_ for accessing the registry.
|
Access the registry anonymously.
|
||||||
|
|
||||||
|
**--raw**
|
||||||
|
|
||||||
|
Output raw manifest or config data depending on --config option.
|
||||||
|
The --format option is not supported with --raw option.
|
||||||
|
|
||||||
|
**--registry-token** _Bearer token_
|
||||||
|
|
||||||
|
Registry token for accessing the registry.
|
||||||
|
|
||||||
|
**--retry-times**
|
||||||
|
|
||||||
|
The number of times to retry; retry wait time will be exponentially increased based on the number of failed attempts.
|
||||||
|
|
||||||
|
**--tls-verify**
|
||||||
|
|
||||||
|
Require HTTPS and verify certificates when talking to container registries (defaults to true).
|
||||||
|
|
||||||
## EXAMPLES
|
## EXAMPLES
|
||||||
|
|
||||||
@ -66,10 +86,19 @@ $ skopeo inspect docker://docker.io/fedora
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ /bin/skopeo inspect --config docker://registry.fedoraproject.org/fedora --format "{{ .Architecture }}"
|
||||||
|
amd64
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ /bin/skopeo inspect --format '{{ .Env }}' docker://registry.access.redhat.com/ubi8
|
||||||
|
[PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin container=oci]
|
||||||
|
```
|
||||||
|
|
||||||
# SEE ALSO
|
# SEE ALSO
|
||||||
skopeo(1), skopeo-login(1), docker-login(1), containers-auth.json(5)
|
skopeo(1), skopeo-login(1), docker-login(1), containers-auth.json(5)
|
||||||
|
|
||||||
## AUTHORS
|
## AUTHORS
|
||||||
|
|
||||||
Antonio Murdaca <runcom@redhat.com>, Miloslav Trmac <mitr@redhat.com>, Jhon Honce <jhonce@redhat.com>
|
Antonio Murdaca <runcom@redhat.com>, Miloslav Trmac <mitr@redhat.com>, Jhon Honce <jhonce@redhat.com>
|
||||||
|
|
||||||
|
20
vendor/github.com/containers/common/pkg/report/camelcase/LICENSE.md
generated
vendored
Normal file
20
vendor/github.com/containers/common/pkg/report/camelcase/LICENSE.md
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Fatih Arslan
|
||||||
|
|
||||||
|
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.
|
58
vendor/github.com/containers/common/pkg/report/camelcase/README.md
generated
vendored
Normal file
58
vendor/github.com/containers/common/pkg/report/camelcase/README.md
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# CamelCase [](http://godoc.org/github.com/fatih/camelcase) [](https://travis-ci.org/fatih/camelcase)
|
||||||
|
|
||||||
|
CamelCase is a Golang (Go) package to split the words of a camelcase type
|
||||||
|
string into a slice of words. It can be used to convert a camelcase word (lower
|
||||||
|
or upper case) into any type of word.
|
||||||
|
|
||||||
|
## Splitting rules:
|
||||||
|
|
||||||
|
1. If string is not valid UTF-8, return it without splitting as
|
||||||
|
single item array.
|
||||||
|
2. Assign all unicode characters into one of 4 sets: lower case
|
||||||
|
letters, upper case letters, numbers, and all other characters.
|
||||||
|
3. Iterate through characters of string, introducing splits
|
||||||
|
between adjacent characters that belong to different sets.
|
||||||
|
4. Iterate through array of split strings, and if a given string
|
||||||
|
is upper case:
|
||||||
|
* if subsequent string is lower case:
|
||||||
|
* move last character of upper case string to beginning of
|
||||||
|
lower case string
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/fatih/camelcase
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage and examples
|
||||||
|
|
||||||
|
```go
|
||||||
|
splitted := camelcase.Split("GolangPackage")
|
||||||
|
|
||||||
|
fmt.Println(splitted[0], splitted[1]) // prints: "Golang", "Package"
|
||||||
|
```
|
||||||
|
|
||||||
|
Both lower camel case and upper camel case are supported. For more info please
|
||||||
|
check: [http://en.wikipedia.org/wiki/CamelCase](http://en.wikipedia.org/wiki/CamelCase)
|
||||||
|
|
||||||
|
Below are some example cases:
|
||||||
|
|
||||||
|
```
|
||||||
|
"" => []
|
||||||
|
"lowercase" => ["lowercase"]
|
||||||
|
"Class" => ["Class"]
|
||||||
|
"MyClass" => ["My", "Class"]
|
||||||
|
"MyC" => ["My", "C"]
|
||||||
|
"HTML" => ["HTML"]
|
||||||
|
"PDFLoader" => ["PDF", "Loader"]
|
||||||
|
"AString" => ["A", "String"]
|
||||||
|
"SimpleXMLParser" => ["Simple", "XML", "Parser"]
|
||||||
|
"vimRPCPlugin" => ["vim", "RPC", "Plugin"]
|
||||||
|
"GL11Version" => ["GL", "11", "Version"]
|
||||||
|
"99Bottles" => ["99", "Bottles"]
|
||||||
|
"May5" => ["May", "5"]
|
||||||
|
"BFG9000" => ["BFG", "9000"]
|
||||||
|
"BöseÜberraschung" => ["Böse", "Überraschung"]
|
||||||
|
"Two spaces" => ["Two", " ", "spaces"]
|
||||||
|
"BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
|
||||||
|
```
|
91
vendor/github.com/containers/common/pkg/report/camelcase/camelcase.go
generated
vendored
Normal file
91
vendor/github.com/containers/common/pkg/report/camelcase/camelcase.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Package camelcase is a micro package to split the words of a camelcase type
|
||||||
|
// string into a slice of words.
|
||||||
|
package camelcase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Split splits the camelcase word and returns a list of words. It also
|
||||||
|
// supports digits. Both lower camel case and upper camel case are supported.
|
||||||
|
// For more info please check: http://en.wikipedia.org/wiki/CamelCase
|
||||||
|
//
|
||||||
|
// Examples
|
||||||
|
//
|
||||||
|
// "" => [""]
|
||||||
|
// "lowercase" => ["lowercase"]
|
||||||
|
// "Class" => ["Class"]
|
||||||
|
// "MyClass" => ["My", "Class"]
|
||||||
|
// "MyC" => ["My", "C"]
|
||||||
|
// "HTML" => ["HTML"]
|
||||||
|
// "PDFLoader" => ["PDF", "Loader"]
|
||||||
|
// "AString" => ["A", "String"]
|
||||||
|
// "SimpleXMLParser" => ["Simple", "XML", "Parser"]
|
||||||
|
// "vimRPCPlugin" => ["vim", "RPC", "Plugin"]
|
||||||
|
// "GL11Version" => ["GL", "11", "Version"]
|
||||||
|
// "99Bottles" => ["99", "Bottles"]
|
||||||
|
// "May5" => ["May", "5"]
|
||||||
|
// "BFG9000" => ["BFG", "9000"]
|
||||||
|
// "BöseÜberraschung" => ["Böse", "Überraschung"]
|
||||||
|
// "Two spaces" => ["Two", " ", "spaces"]
|
||||||
|
// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
|
||||||
|
//
|
||||||
|
// Splitting rules
|
||||||
|
//
|
||||||
|
// 1) If string is not valid UTF-8, return it without splitting as
|
||||||
|
// single item array.
|
||||||
|
// 2) Assign all unicode characters into one of 4 sets: lower case
|
||||||
|
// letters, upper case letters, numbers, and all other characters.
|
||||||
|
// 3) Iterate through characters of string, introducing splits
|
||||||
|
// between adjacent characters that belong to different sets.
|
||||||
|
// 4) Iterate through array of split strings, and if a given string
|
||||||
|
// is upper case:
|
||||||
|
// if subsequent string is lower case:
|
||||||
|
// move last character of upper case string to beginning of
|
||||||
|
// lower case string
|
||||||
|
func Split(src string) (entries []string) {
|
||||||
|
// don't split invalid utf8
|
||||||
|
if !utf8.ValidString(src) {
|
||||||
|
return []string{src}
|
||||||
|
}
|
||||||
|
entries = []string{}
|
||||||
|
var runes [][]rune
|
||||||
|
lastClass := 0
|
||||||
|
class := 0
|
||||||
|
// split into fields based on class of unicode character
|
||||||
|
for _, r := range src {
|
||||||
|
switch {
|
||||||
|
case unicode.IsLower(r):
|
||||||
|
class = 1
|
||||||
|
case unicode.IsUpper(r):
|
||||||
|
class = 2
|
||||||
|
case unicode.IsDigit(r):
|
||||||
|
class = 3
|
||||||
|
default:
|
||||||
|
class = 4
|
||||||
|
}
|
||||||
|
if class == lastClass {
|
||||||
|
runes[len(runes)-1] = append(runes[len(runes)-1], r)
|
||||||
|
} else {
|
||||||
|
runes = append(runes, []rune{r})
|
||||||
|
}
|
||||||
|
lastClass = class
|
||||||
|
}
|
||||||
|
// handle upper case -> lower case sequences, e.g.
|
||||||
|
// "PDFL", "oader" -> "PDF", "Loader"
|
||||||
|
for i := 0; i < len(runes)-1; i++ {
|
||||||
|
if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
|
||||||
|
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
|
||||||
|
runes[i] = runes[i][:len(runes[i])-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// construct []string from results
|
||||||
|
for _, s := range runes {
|
||||||
|
if len(s) > 0 {
|
||||||
|
entries = append(entries, string(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
46
vendor/github.com/containers/common/pkg/report/doc.go
generated
vendored
Normal file
46
vendor/github.com/containers/common/pkg/report/doc.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
Package report provides helper structs/methods/funcs for formatting output
|
||||||
|
|
||||||
|
To format output for an array of structs:
|
||||||
|
|
||||||
|
w := report.NewWriterDefault(os.Stdout)
|
||||||
|
defer w.Flush()
|
||||||
|
|
||||||
|
headers := report.Headers(struct {
|
||||||
|
ID string
|
||||||
|
}{}, nil)
|
||||||
|
t, _ := report.NewTemplate("command name").Parse("{{range .}}{{.ID}}{{end}}")
|
||||||
|
t.Execute(t, headers)
|
||||||
|
t.Execute(t, map[string]string{
|
||||||
|
"ID":"fa85da03b40141899f3af3de6d27852b",
|
||||||
|
})
|
||||||
|
// t.IsTable() == false
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
w := report.NewWriterDefault(os.Stdout)
|
||||||
|
defer w.Flush()
|
||||||
|
|
||||||
|
headers := report.Headers(struct {
|
||||||
|
CID string
|
||||||
|
}{}, map[string]string{
|
||||||
|
"CID":"ID"})
|
||||||
|
t, _ := report.NewTemplate("command name").Parse("table {{.CID}}")
|
||||||
|
t.Execute(t, headers)
|
||||||
|
t.Execute(t,map[string]string{
|
||||||
|
"CID":"fa85da03b40141899f3af3de6d27852b",
|
||||||
|
})
|
||||||
|
// t.IsTable() == true
|
||||||
|
|
||||||
|
Helpers:
|
||||||
|
|
||||||
|
if report.IsJSON(cmd.Flag("format").Value.String()) {
|
||||||
|
... process JSON and output
|
||||||
|
}
|
||||||
|
|
||||||
|
and
|
||||||
|
|
||||||
|
|
||||||
|
Note: Your code should not ignore errors
|
||||||
|
*/
|
||||||
|
package report
|
115
vendor/github.com/containers/common/pkg/report/template.go
generated
vendored
Normal file
115
vendor/github.com/containers/common/pkg/report/template.go
generated
vendored
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/containers/common/pkg/report/camelcase"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Template embeds template.Template to add functionality to methods
|
||||||
|
type Template struct {
|
||||||
|
*template.Template
|
||||||
|
isTable bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncMap is aliased from template.FuncMap
|
||||||
|
type FuncMap template.FuncMap
|
||||||
|
|
||||||
|
// tableReplacer will remove 'table ' prefix and clean up tabs
|
||||||
|
var tableReplacer = strings.NewReplacer(
|
||||||
|
"table ", "",
|
||||||
|
`\t`, "\t",
|
||||||
|
`\n`, "\n",
|
||||||
|
" ", "\t",
|
||||||
|
)
|
||||||
|
|
||||||
|
// escapedReplacer will clean up escaped characters from CLI
|
||||||
|
var escapedReplacer = strings.NewReplacer(
|
||||||
|
`\t`, "\t",
|
||||||
|
`\n`, "\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
// NormalizeFormat reads given go template format provided by CLI and munges it into what we need
|
||||||
|
func NormalizeFormat(format string) string {
|
||||||
|
var f string
|
||||||
|
// two replacers used so we only remove the prefix keyword `table`
|
||||||
|
if strings.HasPrefix(format, "table ") {
|
||||||
|
f = tableReplacer.Replace(format)
|
||||||
|
} else {
|
||||||
|
f = escapedReplacer.Replace(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(f, "\n") {
|
||||||
|
f += "\n"
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers queries the interface for field names.
|
||||||
|
// Array of map is returned to support range templates
|
||||||
|
// Note: unexported fields can be supported by adding field to overrides
|
||||||
|
// Note: It is left to the developer to write out said headers
|
||||||
|
// Podman commands use the general rules of:
|
||||||
|
// 1) unchanged --format includes headers
|
||||||
|
// 2) --format '{{.ID}" # no headers
|
||||||
|
// 3) --format 'table {{.ID}}' # includes headers
|
||||||
|
func Headers(object interface{}, overrides map[string]string) []map[string]string {
|
||||||
|
value := reflect.ValueOf(object)
|
||||||
|
if value.Kind() == reflect.Ptr {
|
||||||
|
value = value.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column header will be field name upper-cased.
|
||||||
|
headers := make(map[string]string, value.NumField())
|
||||||
|
for i := 0; i < value.Type().NumField(); i++ {
|
||||||
|
field := value.Type().Field(i)
|
||||||
|
// Recurse to find field names from promoted structs
|
||||||
|
if field.Type.Kind() == reflect.Struct && field.Anonymous {
|
||||||
|
h := Headers(reflect.New(field.Type).Interface(), nil)
|
||||||
|
for k, v := range h[0] {
|
||||||
|
headers[k] = v
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := strings.Join(camelcase.Split(field.Name), " ")
|
||||||
|
headers[field.Name] = strings.ToUpper(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(overrides) > 0 {
|
||||||
|
// Override column header as provided
|
||||||
|
for k, v := range overrides {
|
||||||
|
headers[k] = strings.ToUpper(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []map[string]string{headers}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTemplate creates a new template object
|
||||||
|
func NewTemplate(name string) *Template {
|
||||||
|
return &Template{template.New(name), false}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses text as a template body for t
|
||||||
|
func (t *Template) Parse(text string) (*Template, error) {
|
||||||
|
if strings.HasPrefix(text, "table ") {
|
||||||
|
t.isTable = true
|
||||||
|
text = "{{range .}}" + NormalizeFormat(text) + "{{end}}"
|
||||||
|
} else {
|
||||||
|
text = NormalizeFormat(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
tt, err := t.Template.Parse(text)
|
||||||
|
return &Template{tt, t.isTable}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funcs adds the elements of the argument map to the template's function map
|
||||||
|
func (t *Template) Funcs(funcMap FuncMap) *Template {
|
||||||
|
return &Template{t.Template.Funcs(template.FuncMap(funcMap)), t.isTable}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTable returns true if format string defines a "table"
|
||||||
|
func (t *Template) IsTable() bool {
|
||||||
|
return t.isTable
|
||||||
|
}
|
13
vendor/github.com/containers/common/pkg/report/validate.go
generated
vendored
Normal file
13
vendor/github.com/containers/common/pkg/report/validate.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package report
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
var jsonRegex = regexp.MustCompile(`^\s*(json|{{\s*json\s*(\.)?\s*}})\s*$`)
|
||||||
|
|
||||||
|
// JSONFormat test CLI --format string to be a JSON request
|
||||||
|
// if report.IsJSON(cmd.Flag("format").Value.String()) {
|
||||||
|
// ... process JSON and output
|
||||||
|
// }
|
||||||
|
func IsJSON(s string) bool {
|
||||||
|
return jsonRegex.MatchString(s)
|
||||||
|
}
|
27
vendor/github.com/containers/common/pkg/report/writer.go
generated
vendored
Normal file
27
vendor/github.com/containers/common/pkg/report/writer.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"text/tabwriter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Writer aliases tabwriter.Writer to provide Podman defaults
|
||||||
|
type Writer struct {
|
||||||
|
*tabwriter.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter initializes a new report.Writer with given values
|
||||||
|
func NewWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) (*Writer, error) {
|
||||||
|
t := tabwriter.NewWriter(output, minwidth, tabwidth, padding, padchar, flags)
|
||||||
|
return &Writer{t}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriterDefault initializes a new report.Writer with Podman defaults
|
||||||
|
func NewWriterDefault(output io.Writer) (*Writer, error) {
|
||||||
|
return NewWriter(output, 12, 2, 2, ' ', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush any output left in buffers
|
||||||
|
func (w *Writer) Flush() error {
|
||||||
|
return w.Writer.Flush()
|
||||||
|
}
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -38,6 +38,8 @@ github.com/containerd/containerd/errdefs
|
|||||||
github.com/containers/common/pkg/auth
|
github.com/containers/common/pkg/auth
|
||||||
github.com/containers/common/pkg/capabilities
|
github.com/containers/common/pkg/capabilities
|
||||||
github.com/containers/common/pkg/completion
|
github.com/containers/common/pkg/completion
|
||||||
|
github.com/containers/common/pkg/report
|
||||||
|
github.com/containers/common/pkg/report/camelcase
|
||||||
github.com/containers/common/pkg/retry
|
github.com/containers/common/pkg/retry
|
||||||
# github.com/containers/image/v5 v5.8.0
|
# github.com/containers/image/v5 v5.8.0
|
||||||
github.com/containers/image/v5/copy
|
github.com/containers/image/v5/copy
|
||||||
|
Loading…
Reference in New Issue
Block a user