containerd/cmd/ctr/commands/commands.go
Sebastiaan van Stijn eaedadbed0
replace strings.Split(N) for strings.Cut() or alternatives
Go 1.18 and up now provides a strings.Cut() which is better suited for
splitting key/value pairs (and similar constructs), and performs better:

```go
func BenchmarkSplit(b *testing.B) {
        b.ReportAllocs()
        data := []string{"12hello=world", "12hello=", "12=hello", "12hello"}
        for i := 0; i < b.N; i++ {
                for _, s := range data {
                        _ = strings.SplitN(s, "=", 2)[0]
                }
        }
}

func BenchmarkCut(b *testing.B) {
        b.ReportAllocs()
        data := []string{"12hello=world", "12hello=", "12=hello", "12hello"}
        for i := 0; i < b.N; i++ {
                for _, s := range data {
                        _, _, _ = strings.Cut(s, "=")
                }
        }
}
```

    BenchmarkSplit
    BenchmarkSplit-10            8244206               128.0 ns/op           128 B/op          4 allocs/op
    BenchmarkCut
    BenchmarkCut-10             54411998                21.80 ns/op            0 B/op          0 allocs/op

While looking at occurrences of `strings.Split()`, I also updated some for alternatives,
or added some constraints; for cases where an specific number of items is expected, I used `strings.SplitN()`
with a suitable limit. This prevents (theoretical) unlimited splits.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-11-07 10:02:25 +01:00

283 lines
7.6 KiB
Go

/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/containerd/containerd/defaults"
"github.com/urfave/cli"
)
var (
// SnapshotterFlags are cli flags specifying snapshotter names
SnapshotterFlags = []cli.Flag{
cli.StringFlag{
Name: "snapshotter",
Usage: "snapshotter name. Empty value stands for the default value.",
EnvVar: "CONTAINERD_SNAPSHOTTER",
},
}
// SnapshotterLabels are cli flags specifying labels which will be added to the new snapshot for container.
SnapshotterLabels = cli.StringSliceFlag{
Name: "snapshotter-label",
Usage: "labels added to the new snapshot for this container.",
}
// LabelFlag is a cli flag specifying labels
LabelFlag = cli.StringSliceFlag{
Name: "label",
Usage: "labels to attach to the image",
}
// RegistryFlags are cli flags specifying registry options
RegistryFlags = []cli.Flag{
cli.BoolFlag{
Name: "skip-verify,k",
Usage: "skip SSL certificate validation",
},
cli.BoolFlag{
Name: "plain-http",
Usage: "allow connections using plain HTTP",
},
cli.StringFlag{
Name: "user,u",
Usage: "user[:password] Registry user and password",
},
cli.StringFlag{
Name: "refresh",
Usage: "refresh token for authorization server",
},
cli.StringFlag{
Name: "hosts-dir",
// compatible with "/etc/docker/certs.d"
Usage: "Custom hosts configuration directory",
},
cli.StringFlag{
Name: "tlscacert",
Usage: "path to TLS root CA",
},
cli.StringFlag{
Name: "tlscert",
Usage: "path to TLS client certificate",
},
cli.StringFlag{
Name: "tlskey",
Usage: "path to TLS client key",
},
cli.BoolFlag{
Name: "http-dump",
Usage: "dump all HTTP request/responses when interacting with container registry",
},
cli.BoolFlag{
Name: "http-trace",
Usage: "enable HTTP tracing for registry interactions",
},
}
// ContainerFlags are cli flags specifying container options
ContainerFlags = []cli.Flag{
cli.StringFlag{
Name: "config,c",
Usage: "path to the runtime-specific spec config file",
},
cli.StringFlag{
Name: "cwd",
Usage: "specify the working directory of the process",
},
cli.StringSliceFlag{
Name: "env",
Usage: "specify additional container environment variables (e.g. FOO=bar)",
},
cli.StringFlag{
Name: "env-file",
Usage: "specify additional container environment variables in a file(e.g. FOO=bar, one per line)",
},
cli.StringSliceFlag{
Name: "label",
Usage: "specify additional labels (e.g. foo=bar)",
},
cli.StringSliceFlag{
Name: "annotation",
Usage: "specify additional OCI annotations (e.g. foo=bar)",
},
cli.StringSliceFlag{
Name: "mount",
Usage: "specify additional container mount (e.g. type=bind,src=/tmp,dst=/host,options=rbind:ro)",
},
cli.BoolFlag{
Name: "net-host",
Usage: "enable host networking for the container",
},
cli.BoolFlag{
Name: "privileged",
Usage: "run privileged container",
},
cli.BoolFlag{
Name: "read-only",
Usage: "set the containers filesystem as readonly",
},
cli.StringFlag{
Name: "runtime",
Usage: "runtime name or absolute path to runtime binary",
Value: defaults.DefaultRuntime,
},
cli.StringFlag{
Name: "runtime-config-path",
Usage: "optional runtime config path",
},
cli.BoolFlag{
Name: "tty,t",
Usage: "allocate a TTY for the container",
},
cli.StringSliceFlag{
Name: "with-ns",
Usage: "specify existing Linux namespaces to join at container runtime (format '<nstype>:<path>')",
},
cli.StringFlag{
Name: "pid-file",
Usage: "file path to write the task's pid",
},
cli.IntSliceFlag{
Name: "gpus",
Usage: "add gpus to the container",
},
cli.BoolFlag{
Name: "allow-new-privs",
Usage: "turn off OCI spec's NoNewPrivileges feature flag",
},
cli.Uint64Flag{
Name: "memory-limit",
Usage: "memory limit (in bytes) for the container",
},
cli.StringSliceFlag{
Name: "cap-add",
Usage: "add Linux capabilities (Set capabilities with 'CAP_' prefix)",
},
cli.StringSliceFlag{
Name: "cap-drop",
Usage: "drop Linux capabilities (Set capabilities with 'CAP_' prefix)",
},
cli.BoolFlag{
Name: "seccomp",
Usage: "enable the default seccomp profile",
},
cli.StringFlag{
Name: "seccomp-profile",
Usage: "file path to custom seccomp profile. seccomp must be set to true, before using seccomp-profile",
},
cli.StringFlag{
Name: "apparmor-default-profile",
Usage: "enable AppArmor with the default profile with the specified name, e.g. \"cri-containerd.apparmor.d\"",
},
cli.StringFlag{
Name: "apparmor-profile",
Usage: "enable AppArmor with an existing custom profile",
},
cli.StringFlag{
Name: "blockio-config-file",
Usage: "file path to blockio class definitions. By default class definitions are not loaded.",
},
cli.StringFlag{
Name: "blockio-class",
Usage: "name of the blockio class to associate the container with",
},
cli.StringFlag{
Name: "rdt-class",
Usage: "name of the RDT class to associate the container with. Specifies a Class of Service (CLOS) for cache and memory bandwidth management.",
},
cli.StringFlag{
Name: "hostname",
Usage: "set the container's host name",
},
cli.StringFlag{
Name: "user,u",
Usage: "username or user id, group optional (format: <name|uid>[:<group|gid>])",
},
}
)
// ObjectWithLabelArgs returns the first arg and a LabelArgs object
func ObjectWithLabelArgs(clicontext *cli.Context) (string, map[string]string) {
var (
first = clicontext.Args().First()
labelStrings = clicontext.Args().Tail()
)
return first, LabelArgs(labelStrings)
}
// LabelArgs returns a map of label key,value pairs
func LabelArgs(labelStrings []string) map[string]string {
labels := make(map[string]string, len(labelStrings))
for _, label := range labelStrings {
key, value, ok := strings.Cut(label, "=")
if !ok {
value = "true"
}
labels[key] = value
}
return labels
}
// AnnotationArgs returns a map of annotation key,value pairs.
func AnnotationArgs(annoStrings []string) (map[string]string, error) {
annotations := make(map[string]string, len(annoStrings))
for _, anno := range annoStrings {
key, value, ok := strings.Cut(anno, "=")
if !ok {
return nil, fmt.Errorf("invalid key=value format annotation: %v", anno)
}
annotations[key] = value
}
return annotations, nil
}
// PrintAsJSON prints input in JSON format
func PrintAsJSON(x interface{}) {
b, err := json.MarshalIndent(x, "", " ")
if err != nil {
fmt.Fprintf(os.Stderr, "can't marshal %+v as a JSON string: %v\n", x, err)
}
fmt.Println(string(b))
}
// WritePidFile writes the pid atomically to a file
func WritePidFile(path string, pid int) error {
path, err := filepath.Abs(path)
if err != nil {
return err
}
tempPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
f, err := os.OpenFile(tempPath, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0666)
if err != nil {
return err
}
_, err = fmt.Fprintf(f, "%d", pid)
f.Close()
if err != nil {
return err
}
return os.Rename(tempPath, path)
}