Files
Daniel J Walsh ed159f864d Vendor in containers/buildah@v1.26.1
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
2022-05-05 04:41:06 -04:00

986 lines
30 KiB
Go

package parse
// this package should contain functions that parse and validate
// user input and is shared either amongst buildah subcommands or
// would be useful to projects vendoring buildah
import (
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"unicode"
"github.com/containerd/containerd/platforms"
"github.com/containers/buildah/define"
internalParse "github.com/containers/buildah/internal/parse"
"github.com/containers/buildah/pkg/sshagent"
"github.com/containers/common/pkg/parse"
"github.com/containers/image/v5/types"
"github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/unshare"
units "github.com/docker/go-units"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/openshift/imagebuilder"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/term"
)
const (
// SeccompDefaultPath defines the default seccomp path
SeccompDefaultPath = "/usr/share/containers/seccomp.json"
// SeccompOverridePath if this exists it overrides the default seccomp path
SeccompOverridePath = "/etc/crio/seccomp.json"
// TypeBind is the type for mounting host dir
TypeBind = "bind"
// TypeTmpfs is the type for mounting tmpfs
TypeTmpfs = "tmpfs"
// TypeCache is the type for mounting a common persistent cache from host
TypeCache = "cache"
// mount=type=cache must create a persistent directory on host so its available for all consecutive builds.
// Lifecycle of following directory will be inherited from how host machine treats temporary directory
BuildahCacheDir = "buildah-cache"
)
// CommonBuildOptions parses the build options from the bud cli
func CommonBuildOptions(c *cobra.Command) (*define.CommonBuildOptions, error) {
return CommonBuildOptionsFromFlagSet(c.Flags(), c.Flag)
}
// CommonBuildOptionsFromFlagSet parses the build options from the bud cli
func CommonBuildOptionsFromFlagSet(flags *pflag.FlagSet, findFlagFunc func(name string) *pflag.Flag) (*define.CommonBuildOptions, error) {
var (
memoryLimit int64
memorySwap int64
noDNS bool
err error
)
memVal, _ := flags.GetString("memory")
if memVal != "" {
memoryLimit, err = units.RAMInBytes(memVal)
if err != nil {
return nil, errors.Wrapf(err, "invalid value for memory")
}
}
memSwapValue, _ := flags.GetString("memory-swap")
if memSwapValue != "" {
if memSwapValue == "-1" {
memorySwap = -1
} else {
memorySwap, err = units.RAMInBytes(memSwapValue)
if err != nil {
return nil, errors.Wrapf(err, "invalid value for memory-swap")
}
}
}
noHosts, _ := flags.GetBool("no-hosts")
addHost, _ := flags.GetStringSlice("add-host")
if len(addHost) > 0 {
if noHosts {
return nil, errors.Errorf("--no-hosts and --add-host conflict, can not be used together")
}
for _, host := range addHost {
if err := validateExtraHost(host); err != nil {
return nil, errors.Wrapf(err, "invalid value for add-host")
}
}
}
noDNS = false
dnsServers := []string{}
if flags.Changed("dns") {
dnsServers, _ = flags.GetStringSlice("dns")
for _, server := range dnsServers {
if strings.ToLower(server) == "none" {
noDNS = true
}
}
if noDNS && len(dnsServers) > 1 {
return nil, errors.Errorf("invalid --dns, --dns=none may not be used with any other --dns options")
}
}
dnsSearch := []string{}
if flags.Changed("dns-search") {
dnsSearch, _ = flags.GetStringSlice("dns-search")
if noDNS && len(dnsSearch) > 0 {
return nil, errors.Errorf("invalid --dns-search, --dns-search may not be used with --dns=none")
}
}
dnsOptions := []string{}
if flags.Changed("dns-option") {
dnsOptions, _ = flags.GetStringSlice("dns-option")
if noDNS && len(dnsOptions) > 0 {
return nil, errors.Errorf("invalid --dns-option, --dns-option may not be used with --dns=none")
}
}
if _, err := units.FromHumanSize(findFlagFunc("shm-size").Value.String()); err != nil {
return nil, errors.Wrapf(err, "invalid --shm-size")
}
volumes, _ := flags.GetStringArray("volume")
if err := Volumes(volumes); err != nil {
return nil, err
}
cpuPeriod, _ := flags.GetUint64("cpu-period")
cpuQuota, _ := flags.GetInt64("cpu-quota")
cpuShares, _ := flags.GetUint64("cpu-shares")
httpProxy, _ := flags.GetBool("http-proxy")
identityLabel, _ := flags.GetBool("identity-label")
ulimit := []string{}
if flags.Changed("ulimit") {
ulimit, _ = flags.GetStringSlice("ulimit")
}
secrets, _ := flags.GetStringArray("secret")
sshsources, _ := flags.GetStringArray("ssh")
commonOpts := &define.CommonBuildOptions{
AddHost: addHost,
CPUPeriod: cpuPeriod,
CPUQuota: cpuQuota,
CPUSetCPUs: findFlagFunc("cpuset-cpus").Value.String(),
CPUSetMems: findFlagFunc("cpuset-mems").Value.String(),
CPUShares: cpuShares,
CgroupParent: findFlagFunc("cgroup-parent").Value.String(),
DNSOptions: dnsOptions,
DNSSearch: dnsSearch,
DNSServers: dnsServers,
HTTPProxy: httpProxy,
IdentityLabel: types.NewOptionalBool(identityLabel),
Memory: memoryLimit,
MemorySwap: memorySwap,
NoHosts: noHosts,
ShmSize: findFlagFunc("shm-size").Value.String(),
Ulimit: ulimit,
Volumes: volumes,
Secrets: secrets,
SSHSources: sshsources,
}
securityOpts, _ := flags.GetStringArray("security-opt")
if err := parseSecurityOpts(securityOpts, commonOpts); err != nil {
return nil, err
}
return commonOpts, nil
}
func parseSecurityOpts(securityOpts []string, commonOpts *define.CommonBuildOptions) error {
for _, opt := range securityOpts {
if opt == "no-new-privileges" {
return errors.Errorf("no-new-privileges is not supported")
}
con := strings.SplitN(opt, "=", 2)
if len(con) != 2 {
return errors.Errorf("invalid --security-opt name=value pair: %q", opt)
}
switch con[0] {
case "label":
commonOpts.LabelOpts = append(commonOpts.LabelOpts, con[1])
case "apparmor":
commonOpts.ApparmorProfile = con[1]
case "seccomp":
commonOpts.SeccompProfilePath = con[1]
default:
return errors.Errorf("invalid --security-opt 2: %q", opt)
}
}
if commonOpts.SeccompProfilePath == "" {
if _, err := os.Stat(SeccompOverridePath); err == nil {
commonOpts.SeccompProfilePath = SeccompOverridePath
} else {
if !os.IsNotExist(err) {
return errors.WithStack(err)
}
if _, err := os.Stat(SeccompDefaultPath); err != nil {
if !os.IsNotExist(err) {
return errors.WithStack(err)
}
} else {
commonOpts.SeccompProfilePath = SeccompDefaultPath
}
}
}
return nil
}
// Split string into slice by colon. Backslash-escaped colon (i.e. "\:") will not be regarded as separator
func SplitStringWithColonEscape(str string) []string {
return internalParse.SplitStringWithColonEscape(str)
}
// Volume parses the input of --volume
func Volume(volume string) (specs.Mount, error) {
return internalParse.Volume(volume)
}
// Volumes validates the host and container paths passed in to the --volume flag
func Volumes(volumes []string) error {
if len(volumes) == 0 {
return nil
}
for _, volume := range volumes {
if _, err := Volume(volume); err != nil {
return err
}
}
return nil
}
// ValidateVolumeHostDir validates a volume mount's source directory
func ValidateVolumeHostDir(hostDir string) error {
return parse.ValidateVolumeHostDir(hostDir)
}
// ValidateVolumeCtrDir validates a volume mount's destination directory.
func ValidateVolumeCtrDir(ctrDir string) error {
return parse.ValidateVolumeCtrDir(ctrDir)
}
// ValidateVolumeOpts validates a volume's options
func ValidateVolumeOpts(options []string) ([]string, error) {
return parse.ValidateVolumeOpts(options)
}
// validateExtraHost validates that the specified string is a valid extrahost and returns it.
// ExtraHost is in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6).
// for add-host flag
func validateExtraHost(val string) error {
// allow for IPv6 addresses in extra hosts by only splitting on first ":"
arr := strings.SplitN(val, ":", 2)
if len(arr) != 2 || len(arr[0]) == 0 {
return errors.Errorf("bad format for add-host: %q", val)
}
if _, err := validateIPAddress(arr[1]); err != nil {
return errors.Errorf("invalid IP address in add-host: %q", arr[1])
}
return nil
}
// validateIPAddress validates an Ip address.
// for dns, ip, and ip6 flags also
func validateIPAddress(val string) (string, error) {
var ip = net.ParseIP(strings.TrimSpace(val))
if ip != nil {
return ip.String(), nil
}
return "", errors.Errorf("%s is not an ip address", val)
}
// SystemContextFromOptions returns a SystemContext populated with values
// per the input parameters provided by the caller for the use in authentication.
func SystemContextFromOptions(c *cobra.Command) (*types.SystemContext, error) {
return SystemContextFromFlagSet(c.Flags(), c.Flag)
}
// SystemContextFromFlagSet returns a SystemContext populated with values
// per the input parameters provided by the caller for the use in authentication.
func SystemContextFromFlagSet(flags *pflag.FlagSet, findFlagFunc func(name string) *pflag.Flag) (*types.SystemContext, error) {
certDir, err := flags.GetString("cert-dir")
if err != nil {
certDir = ""
}
ctx := &types.SystemContext{
DockerCertPath: certDir,
}
tlsVerify, err := flags.GetBool("tls-verify")
if err == nil && findFlagFunc("tls-verify").Changed {
ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!tlsVerify)
ctx.OCIInsecureSkipTLSVerify = !tlsVerify
ctx.DockerDaemonInsecureSkipTLSVerify = !tlsVerify
}
disableCompression, err := flags.GetBool("disable-compression")
if err == nil {
if disableCompression {
ctx.OCIAcceptUncompressedLayers = true
} else {
ctx.DirForceCompress = true
}
}
creds, err := flags.GetString("creds")
if err == nil && findFlagFunc("creds").Changed {
var err error
ctx.DockerAuthConfig, err = AuthConfig(creds)
if err != nil {
return nil, err
}
}
sigPolicy, err := flags.GetString("signature-policy")
if err == nil && findFlagFunc("signature-policy").Changed {
ctx.SignaturePolicyPath = sigPolicy
}
authfile, err := flags.GetString("authfile")
if err == nil {
ctx.AuthFilePath = getAuthFile(authfile)
}
regConf, err := flags.GetString("registries-conf")
if err == nil && findFlagFunc("registries-conf").Changed {
ctx.SystemRegistriesConfPath = regConf
}
regConfDir, err := flags.GetString("registries-conf-dir")
if err == nil && findFlagFunc("registries-conf-dir").Changed {
ctx.RegistriesDirPath = regConfDir
}
shortNameAliasConf, err := flags.GetString("short-name-alias-conf")
if err == nil && findFlagFunc("short-name-alias-conf").Changed {
ctx.UserShortNameAliasConfPath = shortNameAliasConf
}
ctx.DockerRegistryUserAgent = fmt.Sprintf("Buildah/%s", define.Version)
if findFlagFunc("os") != nil && findFlagFunc("os").Changed {
var os string
if os, err = flags.GetString("os"); err != nil {
return nil, err
}
ctx.OSChoice = os
}
if findFlagFunc("arch") != nil && findFlagFunc("arch").Changed {
var arch string
if arch, err = flags.GetString("arch"); err != nil {
return nil, err
}
ctx.ArchitectureChoice = arch
}
if findFlagFunc("variant") != nil && findFlagFunc("variant").Changed {
var variant string
if variant, err = flags.GetString("variant"); err != nil {
return nil, err
}
ctx.VariantChoice = variant
}
if findFlagFunc("platform") != nil && findFlagFunc("platform").Changed {
var specs []string
if specs, err = flags.GetStringSlice("platform"); err != nil {
return nil, err
}
if len(specs) == 0 || specs[0] == "" {
return nil, errors.Errorf("unable to parse --platform value %v", specs)
}
platform := specs[0]
os, arch, variant, err := Platform(platform)
if err != nil {
return nil, err
}
if ctx.OSChoice != "" || ctx.ArchitectureChoice != "" || ctx.VariantChoice != "" {
return nil, errors.Errorf("invalid --platform may not be used with --os, --arch, or --variant")
}
ctx.OSChoice = os
ctx.ArchitectureChoice = arch
ctx.VariantChoice = variant
}
ctx.BigFilesTemporaryDir = GetTempDir()
return ctx, nil
}
func getAuthFile(authfile string) string {
if authfile != "" {
return authfile
}
return os.Getenv("REGISTRY_AUTH_FILE")
}
// PlatformFromOptions parses the operating system (os) and architecture (arch)
// from the provided command line options. Deprecated in favor of
// PlatformsFromOptions(), but kept here because it's part of our API.
func PlatformFromOptions(c *cobra.Command) (os, arch string, err error) {
platforms, err := PlatformsFromOptions(c)
if err != nil {
return "", "", err
}
if len(platforms) < 1 {
return "", "", errors.Errorf("invalid platform syntax for --platform (use OS/ARCH[/VARIANT])")
}
return platforms[0].OS, platforms[0].Arch, nil
}
// PlatformsFromOptions parses the operating system (os) and architecture
// (arch) from the provided command line options. If --platform used, it
// also returns the list of platforms that were passed in as its argument.
func PlatformsFromOptions(c *cobra.Command) (platforms []struct{ OS, Arch, Variant string }, err error) {
var os, arch, variant string
if c.Flag("os").Changed {
if os, err = c.Flags().GetString("os"); err != nil {
return nil, err
}
}
if c.Flag("arch").Changed {
if arch, err = c.Flags().GetString("arch"); err != nil {
return nil, err
}
}
if c.Flag("variant").Changed {
if variant, err = c.Flags().GetString("variant"); err != nil {
return nil, err
}
}
platforms = []struct{ OS, Arch, Variant string }{{os, arch, variant}}
if c.Flag("platform").Changed {
platforms = nil
platformSpecs, err := c.Flags().GetStringSlice("platform")
if err != nil {
return nil, errors.Wrap(err, "unable to parse platform")
}
if os != "" || arch != "" || variant != "" {
return nil, errors.Errorf("invalid --platform may not be used with --os, --arch, or --variant")
}
for _, pf := range platformSpecs {
if os, arch, variant, err = Platform(pf); err != nil {
return nil, errors.Wrapf(err, "unable to parse platform %q", pf)
}
platforms = append(platforms, struct{ OS, Arch, Variant string }{os, arch, variant})
}
}
return platforms, nil
}
const platformSep = "/"
// DefaultPlatform returns the standard platform for the current system
func DefaultPlatform() string {
return platforms.DefaultString()
}
// Platform separates the platform string into os, arch and variant,
// accepting any of $arch, $os/$arch, or $os/$arch/$variant.
func Platform(platform string) (os, arch, variant string, err error) {
split := strings.Split(platform, platformSep)
switch len(split) {
case 3:
variant = split[2]
fallthrough
case 2:
arch = split[1]
os = split[0]
return
case 1:
if platform == "local" {
return Platform(DefaultPlatform())
}
}
return "", "", "", errors.Errorf("invalid platform syntax for %q (use OS/ARCH[/VARIANT][,...])", platform)
}
func parseCreds(creds string) (string, string) {
if creds == "" {
return "", ""
}
up := strings.SplitN(creds, ":", 2)
if len(up) == 1 {
return up[0], ""
}
if up[0] == "" {
return "", up[1]
}
return up[0], up[1]
}
// AuthConfig parses the creds in format [username[:password] into an auth
// config.
func AuthConfig(creds string) (*types.DockerAuthConfig, error) {
username, password := parseCreds(creds)
if username == "" {
fmt.Print("Username: ")
fmt.Scanln(&username)
}
if password == "" {
fmt.Print("Password: ")
termPassword, err := term.ReadPassword(0)
if err != nil {
return nil, errors.Wrapf(err, "could not read password from terminal")
}
password = string(termPassword)
}
return &types.DockerAuthConfig{
Username: username,
Password: password,
}, nil
}
// GetBuildOutput is responsible for parsing custom build output argument i.e `build --output` flag.
// Takes `buildOutput` as string and returns BuildOutputOption
func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) {
if len(buildOutput) == 1 && buildOutput == "-" {
// Feature parity with buildkit, output tar to stdout
// Read more here: https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs
return define.BuildOutputOption{Path: "",
IsDir: false,
IsStdout: true}, nil
}
if !strings.Contains(buildOutput, ",") {
// expect default --output <dirname>
return define.BuildOutputOption{Path: buildOutput,
IsDir: true,
IsStdout: false}, nil
}
isDir := true
isStdout := false
typeSelected := false
pathSelected := false
path := ""
tokens := strings.Split(buildOutput, ",")
for _, option := range tokens {
arr := strings.SplitN(option, "=", 2)
if len(arr) != 2 {
return define.BuildOutputOption{}, fmt.Errorf("invalid build output options %q, expected format key=value", buildOutput)
}
switch arr[0] {
case "type":
if typeSelected {
return define.BuildOutputOption{}, fmt.Errorf("Duplicate %q not supported", arr[0])
}
typeSelected = true
if arr[1] == "local" {
isDir = true
} else if arr[1] == "tar" {
isDir = false
} else {
return define.BuildOutputOption{}, fmt.Errorf("invalid type %q selected for build output options %q", arr[1], buildOutput)
}
case "dest":
if pathSelected {
return define.BuildOutputOption{}, fmt.Errorf("Duplicate %q not supported", arr[0])
}
pathSelected = true
path = arr[1]
default:
return define.BuildOutputOption{}, fmt.Errorf("Unrecognized key %q in build output option: %q", arr[0], buildOutput)
}
}
if !typeSelected || !pathSelected {
return define.BuildOutputOption{}, fmt.Errorf("invalid build output option %q, accepted keys are type and dest must be present", buildOutput)
}
if path == "-" {
if isDir {
return define.BuildOutputOption{}, fmt.Errorf("invalid build output option %q, type=local and dest=- is not supported", buildOutput)
}
return define.BuildOutputOption{Path: "",
IsDir: false,
IsStdout: true}, nil
}
return define.BuildOutputOption{Path: path, IsDir: isDir, IsStdout: isStdout}, nil
}
// IDMappingOptions parses the build options related to user namespaces and ID mapping.
func IDMappingOptions(c *cobra.Command, isolation define.Isolation) (usernsOptions define.NamespaceOptions, idmapOptions *define.IDMappingOptions, err error) {
return IDMappingOptionsFromFlagSet(c.Flags(), c.PersistentFlags(), c.Flag)
}
// IDMappingOptionsFromFlagSet parses the build options related to user namespaces and ID mapping.
func IDMappingOptionsFromFlagSet(flags *pflag.FlagSet, persistentFlags *pflag.FlagSet, findFlagFunc func(name string) *pflag.Flag) (usernsOptions define.NamespaceOptions, idmapOptions *define.IDMappingOptions, err error) {
user := findFlagFunc("userns-uid-map-user").Value.String()
group := findFlagFunc("userns-gid-map-group").Value.String()
// If only the user or group was specified, use the same value for the
// other, since we need both in order to initialize the maps using the
// names.
if user == "" && group != "" {
user = group
}
if group == "" && user != "" {
group = user
}
// Either start with empty maps or the name-based maps.
mappings := idtools.NewIDMappingsFromMaps(nil, nil)
if user != "" && group != "" {
submappings, err := idtools.NewIDMappings(user, group)
if err != nil {
return nil, nil, err
}
mappings = submappings
}
globalOptions := persistentFlags
// We'll parse the UID and GID mapping options the same way.
buildIDMap := func(basemap []idtools.IDMap, option string) ([]specs.LinuxIDMapping, error) {
outmap := make([]specs.LinuxIDMapping, 0, len(basemap))
// Start with the name-based map entries.
for _, m := range basemap {
outmap = append(outmap, specs.LinuxIDMapping{
ContainerID: uint32(m.ContainerID),
HostID: uint32(m.HostID),
Size: uint32(m.Size),
})
}
// Parse the flag's value as one or more triples (if it's even
// been set), and append them.
var spec []string
if globalOptions.Lookup(option) != nil && globalOptions.Lookup(option).Changed {
spec, _ = globalOptions.GetStringSlice(option)
}
if findFlagFunc(option).Changed {
spec, _ = flags.GetStringSlice(option)
}
idmap, err := parseIDMap(spec)
if err != nil {
return nil, err
}
for _, m := range idmap {
outmap = append(outmap, specs.LinuxIDMapping{
ContainerID: m[0],
HostID: m[1],
Size: m[2],
})
}
return outmap, nil
}
uidmap, err := buildIDMap(mappings.UIDs(), "userns-uid-map")
if err != nil {
return nil, nil, err
}
gidmap, err := buildIDMap(mappings.GIDs(), "userns-gid-map")
if err != nil {
return nil, nil, err
}
// If we only have one map or the other populated at this point, then
// use the same mapping for both, since we know that no user or group
// name was specified, but a specific mapping was for one or the other.
if len(uidmap) == 0 && len(gidmap) != 0 {
uidmap = gidmap
}
if len(gidmap) == 0 && len(uidmap) != 0 {
gidmap = uidmap
}
// By default, having mappings configured means we use a user
// namespace. Otherwise, we don't.
usernsOption := define.NamespaceOption{
Name: string(specs.UserNamespace),
Host: len(uidmap) == 0 && len(gidmap) == 0,
}
// If the user specifically requested that we either use or don't use
// user namespaces, override that default.
if findFlagFunc("userns").Changed {
how := findFlagFunc("userns").Value.String()
switch how {
case "", "container", "private":
usernsOption.Host = false
case "host":
usernsOption.Host = true
default:
how = strings.TrimPrefix(how, "ns:")
if _, err := os.Stat(how); err != nil {
return nil, nil, errors.Wrapf(err, "checking %s namespace", string(specs.UserNamespace))
}
logrus.Debugf("setting %q namespace to %q", string(specs.UserNamespace), how)
usernsOption.Path = how
}
}
usernsOptions = define.NamespaceOptions{usernsOption}
// If the user requested that we use the host namespace, but also that
// we use mappings, that's not going to work.
if (len(uidmap) != 0 || len(gidmap) != 0) && usernsOption.Host {
return nil, nil, errors.Errorf("can not specify ID mappings while using host's user namespace")
}
return usernsOptions, &define.IDMappingOptions{
HostUIDMapping: usernsOption.Host,
HostGIDMapping: usernsOption.Host,
UIDMap: uidmap,
GIDMap: gidmap,
}, nil
}
func parseIDMap(spec []string) (m [][3]uint32, err error) {
for _, s := range spec {
args := strings.FieldsFunc(s, func(r rune) bool { return !unicode.IsDigit(r) })
if len(args)%3 != 0 {
return nil, errors.Errorf("mapping %q is not in the form containerid:hostid:size[,...]", s)
}
for len(args) >= 3 {
cid, err := strconv.ParseUint(args[0], 10, 32)
if err != nil {
return nil, errors.Wrapf(err, "error parsing container ID %q from mapping %q as a number", args[0], s)
}
hostid, err := strconv.ParseUint(args[1], 10, 32)
if err != nil {
return nil, errors.Wrapf(err, "error parsing host ID %q from mapping %q as a number", args[1], s)
}
size, err := strconv.ParseUint(args[2], 10, 32)
if err != nil {
return nil, errors.Wrapf(err, "error parsing %q from mapping %q as a number", args[2], s)
}
m = append(m, [3]uint32{uint32(cid), uint32(hostid), uint32(size)})
args = args[3:]
}
}
return m, nil
}
// NamespaceOptions parses the build options for all namespaces except for user namespace.
func NamespaceOptions(c *cobra.Command) (namespaceOptions define.NamespaceOptions, networkPolicy define.NetworkConfigurationPolicy, err error) {
return NamespaceOptionsFromFlagSet(c.Flags(), c.Flag)
}
// NamespaceOptionsFromFlagSet parses the build options for all namespaces except for user namespace.
func NamespaceOptionsFromFlagSet(flags *pflag.FlagSet, findFlagFunc func(name string) *pflag.Flag) (namespaceOptions define.NamespaceOptions, networkPolicy define.NetworkConfigurationPolicy, err error) {
options := make(define.NamespaceOptions, 0, 7)
policy := define.NetworkDefault
for _, what := range []string{"cgroupns", string(specs.IPCNamespace), "network", string(specs.PIDNamespace), string(specs.UTSNamespace)} {
if flags.Lookup(what) != nil && findFlagFunc(what).Changed {
how := findFlagFunc(what).Value.String()
switch what {
case "cgroupns":
what = string(specs.CgroupNamespace)
}
switch how {
case "", "container", "private":
logrus.Debugf("setting %q namespace to %q", what, "")
policy = define.NetworkEnabled
options.AddOrReplace(define.NamespaceOption{
Name: what,
})
case "host":
logrus.Debugf("setting %q namespace to host", what)
policy = define.NetworkEnabled
options.AddOrReplace(define.NamespaceOption{
Name: what,
Host: true,
})
default:
if what == string(specs.NetworkNamespace) {
if how == "none" {
options.AddOrReplace(define.NamespaceOption{
Name: what,
})
policy = define.NetworkDisabled
logrus.Debugf("setting network to disabled")
break
}
}
how = strings.TrimPrefix(how, "ns:")
// if not a path we assume it is a comma separated network list, see setupNamespaces() in run_linux.go
if filepath.IsAbs(how) || what != string(specs.NetworkNamespace) {
if _, err := os.Stat(how); err != nil {
return nil, define.NetworkDefault, errors.Wrapf(err, "checking %s namespace", what)
}
}
policy = define.NetworkEnabled
logrus.Debugf("setting %q namespace to %q", what, how)
options.AddOrReplace(define.NamespaceOption{
Name: what,
Path: how,
})
}
}
}
return options, policy, nil
}
func defaultIsolation() (define.Isolation, error) {
isolation, isSet := os.LookupEnv("BUILDAH_ISOLATION")
if isSet {
switch strings.ToLower(isolation) {
case "oci":
return define.IsolationOCI, nil
case "rootless":
return define.IsolationOCIRootless, nil
case "chroot":
return define.IsolationChroot, nil
default:
return 0, errors.Errorf("unrecognized $BUILDAH_ISOLATION value %q", isolation)
}
}
if unshare.IsRootless() {
return define.IsolationOCIRootless, nil
}
return define.IsolationDefault, nil
}
// IsolationOption parses the --isolation flag.
func IsolationOption(isolation string) (define.Isolation, error) {
if isolation != "" {
switch strings.ToLower(isolation) {
case "oci", "default":
return define.IsolationOCI, nil
case "rootless":
return define.IsolationOCIRootless, nil
case "chroot":
return define.IsolationChroot, nil
default:
return 0, errors.Errorf("unrecognized isolation type %q", isolation)
}
}
return defaultIsolation()
}
// Device parses device mapping string to a src, dest & permissions string
// Valid values for device look like:
// '/dev/sdc"
// '/dev/sdc:/dev/xvdc"
// '/dev/sdc:/dev/xvdc:rwm"
// '/dev/sdc:rm"
func Device(device string) (string, string, string, error) {
src := ""
dst := ""
permissions := "rwm"
arr := strings.Split(device, ":")
switch len(arr) {
case 3:
if !isValidDeviceMode(arr[2]) {
return "", "", "", errors.Errorf("invalid device mode: %s", arr[2])
}
permissions = arr[2]
fallthrough
case 2:
if isValidDeviceMode(arr[1]) {
permissions = arr[1]
} else {
if len(arr[1]) == 0 || arr[1][0] != '/' {
return "", "", "", errors.Errorf("invalid device mode: %s", arr[1])
}
dst = arr[1]
}
fallthrough
case 1:
if len(arr[0]) > 0 {
src = arr[0]
break
}
fallthrough
default:
return "", "", "", errors.Errorf("invalid device specification: %s", device)
}
if dst == "" {
dst = src
}
return src, dst, permissions, nil
}
// isValidDeviceMode checks if the mode for device is valid or not.
// isValid mode is a composition of r (read), w (write), and m (mknod).
func isValidDeviceMode(mode string) bool {
var legalDeviceMode = map[rune]bool{
'r': true,
'w': true,
'm': true,
}
if mode == "" {
return false
}
for _, c := range mode {
if !legalDeviceMode[c] {
return false
}
legalDeviceMode[c] = false
}
return true
}
func GetTempDir() string {
if tmpdir, ok := os.LookupEnv("TMPDIR"); ok {
return tmpdir
}
return "/var/tmp"
}
// Secrets parses the --secret flag
func Secrets(secrets []string) (map[string]define.Secret, error) {
invalidSyntax := errors.Errorf("incorrect secret flag format: should be --secret id=foo,src=bar[,env=ENV,type=file|env]")
parsed := make(map[string]define.Secret)
for _, secret := range secrets {
tokens := strings.Split(secret, ",")
var id, src, typ string
for _, val := range tokens {
kv := strings.SplitN(val, "=", 2)
switch kv[0] {
case "id":
id = kv[1]
case "src":
src = kv[1]
case "env":
src = kv[1]
typ = "env"
case "type":
if kv[1] != "file" && kv[1] != "env" {
return nil, errors.New("invalid secret type, must be file or env")
}
typ = kv[1]
}
}
if id == "" {
return nil, invalidSyntax
}
if src == "" {
src = id
}
if typ == "" {
if _, ok := os.LookupEnv(id); ok {
typ = "env"
} else {
typ = "file"
}
}
if typ == "file" {
fullPath, err := filepath.Abs(src)
if err != nil {
return nil, errors.Wrap(err, "could not parse secrets")
}
_, err = os.Stat(fullPath)
if err != nil {
return nil, errors.Wrap(err, "could not parse secrets")
}
src = fullPath
}
newSecret := define.Secret{
Source: src,
SourceType: typ,
}
parsed[id] = newSecret
}
return parsed, nil
}
// SSH parses the --ssh flag
func SSH(sshSources []string) (map[string]*sshagent.Source, error) {
parsed := make(map[string]*sshagent.Source)
var paths []string
for _, v := range sshSources {
parts := strings.SplitN(v, "=", 2)
if len(parts) > 1 {
paths = strings.Split(parts[1], ",")
}
source, err := sshagent.NewSource(paths)
if err != nil {
return nil, err
}
parsed[parts[0]] = source
}
return parsed, nil
}
func ContainerIgnoreFile(contextDir, path string) ([]string, string, error) {
if path != "" {
excludes, err := imagebuilder.ParseIgnore(path)
return excludes, path, err
}
path = filepath.Join(contextDir, ".containerignore")
excludes, err := imagebuilder.ParseIgnore(path)
if os.IsNotExist(err) {
path = filepath.Join(contextDir, ".dockerignore")
excludes, err = imagebuilder.ParseIgnore(path)
}
if os.IsNotExist(err) {
return excludes, "", nil
}
return excludes, path, err
}