2018-04-19 03:00:15 +08:00
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"
2018-04-30 06:36:35 +08:00
"path/filepath"
2020-01-20 19:31:09 +08:00
"runtime"
2018-06-07 12:45:17 +08:00
"strconv"
2018-04-19 03:00:15 +08:00
"strings"
2018-06-07 12:45:17 +08:00
"unicode"
2018-04-19 03:00:15 +08:00
2018-09-18 03:20:16 +08:00
"github.com/containers/buildah"
2019-10-26 05:19:30 +08:00
"github.com/containers/image/v5/types"
2018-06-07 12:45:17 +08:00
"github.com/containers/storage/pkg/idtools"
2020-07-07 22:15:40 +08:00
"github.com/containers/storage/pkg/unshare"
2019-10-02 04:03:57 +08:00
units "github.com/docker/go-units"
specs "github.com/opencontainers/runtime-spec/specs-go"
2018-04-19 03:00:15 +08:00
"github.com/pkg/errors"
2018-06-07 12:45:17 +08:00
"github.com/sirupsen/logrus"
2019-04-07 00:03:58 +08:00
"github.com/spf13/cobra"
2018-04-19 03:00:15 +08:00
"golang.org/x/crypto/ssh/terminal"
)
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"
2019-06-20 02:17:11 +08:00
// TypeBind is the type for mounting host dir
TypeBind = "bind"
// TypeTmpfs is the type for mounting tmpfs
TypeTmpfs = "tmpfs"
)
var (
errBadMntOption = errors . Errorf ( "invalid mount option" )
errDuplicateDest = errors . Errorf ( "duplicate mount destination" )
optionArgError = errors . Errorf ( "must provide an argument for option" )
noDestError = errors . Errorf ( "must set volume destination" )
2018-04-19 03:00:15 +08:00
)
2018-06-07 12:45:17 +08:00
// CommonBuildOptions parses the build options from the bud cli
2020-02-08 01:54:18 +08:00
func CommonBuildOptions ( c * cobra . Command ) ( * buildah . CommonBuildOptions , error ) {
2018-04-19 03:00:15 +08:00
var (
memoryLimit int64
memorySwap int64
2019-06-07 03:44:23 +08:00
noDNS bool
2018-04-19 03:00:15 +08:00
err error
)
2019-04-26 03:39:49 +08:00
2019-01-22 23:35:52 +08:00
memVal , _ := c . Flags ( ) . GetString ( "memory" )
if memVal != "" {
memoryLimit , err = units . RAMInBytes ( memVal )
2018-04-19 03:00:15 +08:00
if err != nil {
return nil , errors . Wrapf ( err , "invalid value for memory" )
}
}
2019-01-22 23:35:52 +08:00
memSwapValue , _ := c . Flags ( ) . GetString ( "memory-swap" )
if memSwapValue != "" {
memorySwap , err = units . RAMInBytes ( memSwapValue )
2018-04-19 03:00:15 +08:00
if err != nil {
return nil , errors . Wrapf ( err , "invalid value for memory-swap" )
}
}
2019-01-22 23:35:52 +08:00
addHost , _ := c . Flags ( ) . GetStringSlice ( "add-host" )
if len ( addHost ) > 0 {
for _ , host := range addHost {
2018-04-19 03:00:15 +08:00
if err := validateExtraHost ( host ) ; err != nil {
return nil , errors . Wrapf ( err , "invalid value for add-host" )
}
}
}
2019-04-07 00:03:58 +08:00
2019-06-07 03:44:23 +08:00
noDNS = false
2020-02-08 01:54:18 +08:00
dnsServers := [ ] string { }
if c . Flag ( "dns" ) . Changed {
dnsServers , _ = c . 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" )
2019-06-07 03:44:23 +08:00
}
}
2020-02-08 01:54:18 +08:00
dnsSearch := [ ] string { }
if c . Flag ( "dns-search" ) . Changed {
dnsSearch , _ = c . 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" )
}
2019-06-07 03:44:23 +08:00
}
2020-02-08 01:54:18 +08:00
dnsOptions := [ ] string { }
2020-05-01 05:32:57 +08:00
if c . Flag ( "dns-option" ) . Changed {
2020-02-08 01:54:18 +08:00
dnsOptions , _ = c . 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" )
}
2019-06-07 03:44:23 +08:00
}
2019-04-07 00:03:58 +08:00
2019-01-22 23:35:52 +08:00
if _ , err := units . FromHumanSize ( c . Flag ( "shm-size" ) . Value . String ( ) ) ; err != nil {
2018-04-19 03:00:15 +08:00
return nil , errors . Wrapf ( err , "invalid --shm-size" )
}
2019-12-22 05:43:59 +08:00
volumes , _ := c . Flags ( ) . GetStringArray ( "volume" )
2019-07-24 19:55:25 +08:00
if err := Volumes ( volumes ) ; err != nil {
2018-04-19 03:00:15 +08:00
return nil , err
}
2019-01-22 23:35:52 +08:00
cpuPeriod , _ := c . Flags ( ) . GetUint64 ( "cpu-period" )
cpuQuota , _ := c . Flags ( ) . GetInt64 ( "cpu-quota" )
2019-06-11 03:31:50 +08:00
cpuShares , _ := c . Flags ( ) . GetUint64 ( "cpu-shares" )
2019-04-13 07:03:39 +08:00
httpProxy , _ := c . Flags ( ) . GetBool ( "http-proxy" )
2020-02-08 01:54:18 +08:00
ulimit := [ ] string { }
if c . Flag ( "ulimit" ) . Changed {
ulimit , _ = c . Flags ( ) . GetStringSlice ( "ulimit" )
}
2020-01-14 20:12:56 +08:00
2018-04-19 03:00:15 +08:00
commonOpts := & buildah . CommonBuildOptions {
2019-01-22 23:35:52 +08:00
AddHost : addHost ,
CPUPeriod : cpuPeriod ,
CPUQuota : cpuQuota ,
CPUSetCPUs : c . Flag ( "cpuset-cpus" ) . Value . String ( ) ,
CPUSetMems : c . Flag ( "cpuset-mems" ) . Value . String ( ) ,
CPUShares : cpuShares ,
2020-06-22 18:44:23 +08:00
CgroupParent : c . Flag ( "cgroup-parent" ) . Value . String ( ) ,
DNSOptions : dnsOptions ,
2019-04-07 00:03:58 +08:00
DNSSearch : dnsSearch ,
DNSServers : dnsServers ,
2019-04-13 07:03:39 +08:00
HTTPProxy : httpProxy ,
2018-04-19 03:00:15 +08:00
Memory : memoryLimit ,
MemorySwap : memorySwap ,
2019-01-22 23:35:52 +08:00
ShmSize : c . Flag ( "shm-size" ) . Value . String ( ) ,
2020-02-08 01:54:18 +08:00
Ulimit : ulimit ,
2019-01-22 23:35:52 +08:00
Volumes : volumes ,
2018-04-19 03:00:15 +08:00
}
2019-04-03 01:41:03 +08:00
securityOpts , _ := c . Flags ( ) . GetStringArray ( "security-opt" )
2019-01-22 23:35:52 +08:00
if err := parseSecurityOpts ( securityOpts , commonOpts ) ; err != nil {
2018-04-19 03:00:15 +08:00
return nil , err
}
return commonOpts , nil
}
func parseSecurityOpts ( securityOpts [ ] string , commonOpts * buildah . 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 {
2018-03-07 07:13:24 +08:00
return errors . Errorf ( "Invalid --security-opt name=value pair: %q" , opt )
2018-04-19 03:00:15 +08:00
}
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 . Wrapf ( err , "can't check if %q exists" , SeccompOverridePath )
}
if _ , err := os . Stat ( SeccompDefaultPath ) ; err != nil {
if ! os . IsNotExist ( err ) {
return errors . Wrapf ( err , "can't check if %q exists" , SeccompDefaultPath )
}
} else {
commonOpts . SeccompProfilePath = SeccompDefaultPath
}
}
}
return nil
}
2019-07-24 19:55:25 +08:00
// Volume parses the input of --volume
func Volume ( volume string ) ( specs . Mount , error ) {
2019-04-28 18:33:09 +08:00
mount := specs . Mount { }
arr := strings . SplitN ( volume , ":" , 3 )
if len ( arr ) < 2 {
return mount , errors . Errorf ( "incorrect volume format %q, should be host-dir:ctr-dir[:option]" , volume )
}
2019-06-20 02:17:11 +08:00
if err := validateVolumeMountHostDir ( arr [ 0 ] ) ; err != nil {
2019-04-28 18:33:09 +08:00
return mount , err
}
2019-05-04 04:45:01 +08:00
if err := ValidateVolumeCtrDir ( arr [ 1 ] ) ; err != nil {
2019-04-28 18:33:09 +08:00
return mount , err
}
mountOptions := ""
if len ( arr ) > 2 {
mountOptions = arr [ 2 ]
2019-06-29 04:07:38 +08:00
if _ , err := ValidateVolumeOpts ( strings . Split ( arr [ 2 ] , "," ) ) ; err != nil {
2019-04-28 18:33:09 +08:00
return mount , err
}
}
mountOpts := strings . Split ( mountOptions , "," )
mount . Source = arr [ 0 ]
mount . Destination = arr [ 1 ]
mount . Type = "rbind"
mount . Options = mountOpts
return mount , nil
}
2019-07-24 19:55:25 +08:00
// Volumes validates the host and container paths passed in to the --volume flag
func Volumes ( volumes [ ] string ) error {
2018-04-19 03:00:15 +08:00
if len ( volumes ) == 0 {
return nil
}
for _ , volume := range volumes {
2019-07-24 19:55:25 +08:00
if _ , err := Volume ( volume ) ; err != nil {
2018-04-19 03:00:15 +08:00
return err
}
}
return nil
}
2019-06-20 02:17:11 +08:00
func getVolumeMounts ( volumes [ ] string ) ( map [ string ] specs . Mount , error ) {
finalVolumeMounts := make ( map [ string ] specs . Mount )
for _ , volume := range volumes {
2019-07-24 19:55:25 +08:00
volumeMount , err := Volume ( volume )
2019-06-20 02:17:11 +08:00
if err != nil {
return nil , err
}
if _ , ok := finalVolumeMounts [ volumeMount . Destination ] ; ok {
return nil , errors . Wrapf ( errDuplicateDest , volumeMount . Destination )
}
finalVolumeMounts [ volumeMount . Destination ] = volumeMount
}
return finalVolumeMounts , nil
}
// GetVolumes gets the volumes from --volume and --mount
func GetVolumes ( volumes [ ] string , mounts [ ] string ) ( [ ] specs . Mount , error ) {
unifiedMounts , err := getMounts ( mounts )
if err != nil {
return nil , err
}
volumeMounts , err := getVolumeMounts ( volumes )
if err != nil {
return nil , err
}
for dest , mount := range volumeMounts {
if _ , ok := unifiedMounts [ dest ] ; ok {
return nil , errors . Wrapf ( errDuplicateDest , dest )
}
unifiedMounts [ dest ] = mount
}
finalMounts := make ( [ ] specs . Mount , 0 , len ( unifiedMounts ) )
for _ , mount := range unifiedMounts {
finalMounts = append ( finalMounts , mount )
}
return finalMounts , nil
}
// getMounts takes user-provided input from the --mount flag and creates OCI
// spec mounts.
// buildah run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
// buildah run --mount type=tmpfs,target=/dev/shm ...
func getMounts ( mounts [ ] string ) ( map [ string ] specs . Mount , error ) {
finalMounts := make ( map [ string ] specs . Mount )
errInvalidSyntax := errors . Errorf ( "incorrect mount format: should be --mount type=<bind|tmpfs>,[src=<host-dir>,]target=<ctr-dir>[,options]" )
// TODO(vrothberg): the manual parsing can be replaced with a regular expression
// to allow a more robust parsing of the mount format and to give
2019-11-17 00:31:41 +08:00
// precise errors regarding supported format versus supported options.
2019-06-20 02:17:11 +08:00
for _ , mount := range mounts {
arr := strings . SplitN ( mount , "," , 2 )
if len ( arr ) < 2 {
return nil , errors . Wrapf ( errInvalidSyntax , "%q" , mount )
}
kv := strings . Split ( arr [ 0 ] , "=" )
// TODO: type is not explicitly required in Docker.
// If not specified, it defaults to "volume".
if len ( kv ) != 2 || kv [ 0 ] != "type" {
return nil , errors . Wrapf ( errInvalidSyntax , "%q" , mount )
}
tokens := strings . Split ( arr [ 1 ] , "," )
switch kv [ 1 ] {
case TypeBind :
mount , err := GetBindMount ( tokens )
if err != nil {
return nil , err
}
if _ , ok := finalMounts [ mount . Destination ] ; ok {
return nil , errors . Wrapf ( errDuplicateDest , mount . Destination )
}
finalMounts [ mount . Destination ] = mount
case TypeTmpfs :
mount , err := GetTmpfsMount ( tokens )
if err != nil {
return nil , err
}
if _ , ok := finalMounts [ mount . Destination ] ; ok {
return nil , errors . Wrapf ( errDuplicateDest , mount . Destination )
}
finalMounts [ mount . Destination ] = mount
default :
return nil , errors . Errorf ( "invalid filesystem type %q" , kv [ 1 ] )
}
}
return finalMounts , nil
}
// GetBindMount parses a single bind mount entry from the --mount flag.
func GetBindMount ( args [ ] string ) ( specs . Mount , error ) {
newMount := specs . Mount {
Type : TypeBind ,
}
setSource := false
setDest := false
for _ , val := range args {
2020-09-24 06:09:01 +08:00
kv := strings . SplitN ( val , "=" , 2 )
2019-06-20 02:17:11 +08:00
switch kv [ 0 ] {
case "bind-nonrecursive" :
newMount . Options = append ( newMount . Options , "bind" )
case "ro" , "nosuid" , "nodev" , "noexec" :
// TODO: detect duplication of these options.
// (Is this necessary?)
newMount . Options = append ( newMount . Options , kv [ 0 ] )
2020-07-03 20:48:00 +08:00
case "readonly" :
// Alias for "ro"
newMount . Options = append ( newMount . Options , "ro" )
2019-06-20 02:17:11 +08:00
case "shared" , "rshared" , "private" , "rprivate" , "slave" , "rslave" , "Z" , "z" :
newMount . Options = append ( newMount . Options , kv [ 0 ] )
case "bind-propagation" :
if len ( kv ) == 1 {
return newMount , errors . Wrapf ( optionArgError , kv [ 0 ] )
}
newMount . Options = append ( newMount . Options , kv [ 1 ] )
case "src" , "source" :
if len ( kv ) == 1 {
return newMount , errors . Wrapf ( optionArgError , kv [ 0 ] )
}
if err := ValidateVolumeHostDir ( kv [ 1 ] ) ; err != nil {
return newMount , err
}
newMount . Source = kv [ 1 ]
setSource = true
case "target" , "dst" , "destination" :
if len ( kv ) == 1 {
return newMount , errors . Wrapf ( optionArgError , kv [ 0 ] )
}
if err := ValidateVolumeCtrDir ( kv [ 1 ] ) ; err != nil {
return newMount , err
}
newMount . Destination = kv [ 1 ]
setDest = true
2020-07-03 18:59:07 +08:00
case "consistency" :
// Option for OS X only, has no meaning on other platforms
// and can thus be safely ignored.
// See also the handling of the equivalent "delegated" and "cached" in ValidateVolumeOpts
2019-06-20 02:17:11 +08:00
default :
return newMount , errors . Wrapf ( errBadMntOption , kv [ 0 ] )
}
}
if ! setDest {
return newMount , noDestError
}
if ! setSource {
newMount . Source = newMount . Destination
}
opts , err := ValidateVolumeOpts ( newMount . Options )
if err != nil {
return newMount , err
}
newMount . Options = opts
return newMount , nil
}
// GetTmpfsMount parses a single tmpfs mount entry from the --mount flag
func GetTmpfsMount ( args [ ] string ) ( specs . Mount , error ) {
newMount := specs . Mount {
Type : TypeTmpfs ,
Source : TypeTmpfs ,
}
setDest := false
for _ , val := range args {
2020-09-24 06:09:01 +08:00
kv := strings . SplitN ( val , "=" , 2 )
2019-06-20 02:17:11 +08:00
switch kv [ 0 ] {
case "ro" , "nosuid" , "nodev" , "noexec" :
newMount . Options = append ( newMount . Options , kv [ 0 ] )
2020-07-03 20:48:00 +08:00
case "readonly" :
// Alias for "ro"
newMount . Options = append ( newMount . Options , "ro" )
2019-06-20 02:17:11 +08:00
case "tmpfs-mode" :
if len ( kv ) == 1 {
return newMount , errors . Wrapf ( optionArgError , kv [ 0 ] )
}
newMount . Options = append ( newMount . Options , fmt . Sprintf ( "mode=%s" , kv [ 1 ] ) )
case "tmpfs-size" :
if len ( kv ) == 1 {
return newMount , errors . Wrapf ( optionArgError , kv [ 0 ] )
}
newMount . Options = append ( newMount . Options , fmt . Sprintf ( "size=%s" , kv [ 1 ] ) )
case "src" , "source" :
return newMount , errors . Errorf ( "source is not supported with tmpfs mounts" )
case "target" , "dst" , "destination" :
if len ( kv ) == 1 {
return newMount , errors . Wrapf ( optionArgError , kv [ 0 ] )
}
if err := ValidateVolumeCtrDir ( kv [ 1 ] ) ; err != nil {
return newMount , err
}
newMount . Destination = kv [ 1 ]
setDest = true
default :
return newMount , errors . Wrapf ( errBadMntOption , kv [ 0 ] )
}
}
if ! setDest {
return newMount , noDestError
}
return newMount , nil
}
// ValidateVolumeHostDir validates a volume mount's source directory
2019-05-04 04:45:01 +08:00
func ValidateVolumeHostDir ( hostDir string ) error {
2019-06-20 02:17:11 +08:00
if len ( hostDir ) == 0 {
return errors . Errorf ( "host directory cannot be empty" )
}
if filepath . IsAbs ( hostDir ) {
if _ , err := os . Stat ( hostDir ) ; err != nil {
return errors . Wrapf ( err , "error checking path %q" , hostDir )
}
}
// If hostDir is not an absolute path, that means the user wants to create a
// named volume. This will be done later on in the code.
return nil
}
// validates the host path of buildah --volume
func validateVolumeMountHostDir ( hostDir string ) error {
2018-04-30 06:36:35 +08:00
if ! filepath . IsAbs ( hostDir ) {
return errors . Errorf ( "invalid host path, must be an absolute path %q" , hostDir )
}
2018-04-19 03:00:15 +08:00
if _ , err := os . Stat ( hostDir ) ; err != nil {
return errors . Wrapf ( err , "error checking path %q" , hostDir )
}
return nil
}
2019-06-20 02:17:11 +08:00
// ValidateVolumeCtrDir validates a volume mount's destination directory.
2019-05-04 04:45:01 +08:00
func ValidateVolumeCtrDir ( ctrDir string ) error {
2019-06-20 02:17:11 +08:00
if len ( ctrDir ) == 0 {
return errors . Errorf ( "container directory cannot be empty" )
}
2018-04-30 06:36:35 +08:00
if ! filepath . IsAbs ( ctrDir ) {
2019-06-20 02:17:11 +08:00
return errors . Errorf ( "invalid container path %q, must be an absolute path" , ctrDir )
2018-04-19 03:00:15 +08:00
}
return nil
}
2019-06-29 04:07:38 +08:00
// ValidateVolumeOpts validates a volume's options
func ValidateVolumeOpts ( options [ ] string ) ( [ ] string , error ) {
2020-11-18 22:50:53 +08:00
var foundRootPropagation , foundRWRO , foundLabelChange , bindType , foundExec , foundDev , foundSuid , foundChown int
2019-06-29 04:07:38 +08:00
finalOpts := make ( [ ] string , 0 , len ( options ) )
2018-04-19 03:00:15 +08:00
for _ , opt := range options {
switch opt {
2019-08-15 22:14:27 +08:00
case "noexec" , "exec" :
foundExec ++
if foundExec > 1 {
return nil , errors . Errorf ( "invalid options %q, can only specify 1 'noexec' or 'exec' option" , strings . Join ( options , ", " ) )
}
case "nodev" , "dev" :
foundDev ++
if foundDev > 1 {
return nil , errors . Errorf ( "invalid options %q, can only specify 1 'nodev' or 'dev' option" , strings . Join ( options , ", " ) )
}
case "nosuid" , "suid" :
foundSuid ++
if foundSuid > 1 {
return nil , errors . Errorf ( "invalid options %q, can only specify 1 'nosuid' or 'suid' option" , strings . Join ( options , ", " ) )
}
2018-04-19 03:00:15 +08:00
case "rw" , "ro" :
2019-08-15 22:14:27 +08:00
foundRWRO ++
2018-04-19 03:00:15 +08:00
if foundRWRO > 1 {
2019-06-29 04:07:38 +08:00
return nil , errors . Errorf ( "invalid options %q, can only specify 1 'rw' or 'ro' option" , strings . Join ( options , ", " ) )
2018-04-19 03:00:15 +08:00
}
2019-04-29 21:41:18 +08:00
case "z" , "Z" , "O" :
2019-08-15 22:14:27 +08:00
foundLabelChange ++
2018-04-19 03:00:15 +08:00
if foundLabelChange > 1 {
2019-06-29 04:07:38 +08:00
return nil , errors . Errorf ( "invalid options %q, can only specify 1 'z', 'Z', or 'O' option" , strings . Join ( options , ", " ) )
2018-04-19 03:00:15 +08:00
}
2020-11-18 22:50:53 +08:00
case "U" :
foundChown ++
if foundChown > 1 {
return nil , errors . Errorf ( "invalid options %q, can only specify 1 'U' option" , strings . Join ( options , ", " ) )
}
2018-05-07 16:28:32 +08:00
case "private" , "rprivate" , "shared" , "rshared" , "slave" , "rslave" , "unbindable" , "runbindable" :
2019-08-15 22:14:27 +08:00
foundRootPropagation ++
2018-04-19 03:00:15 +08:00
if foundRootPropagation > 1 {
2019-06-29 04:07:38 +08:00
return nil , errors . Errorf ( "invalid options %q, can only specify 1 '[r]shared', '[r]private', '[r]slave' or '[r]unbindable' option" , strings . Join ( options , ", " ) )
2018-04-19 03:00:15 +08:00
}
2019-06-29 04:07:38 +08:00
case "bind" , "rbind" :
bindType ++
if bindType > 1 {
return nil , errors . Errorf ( "invalid options %q, can only specify 1 '[r]bind' option" , strings . Join ( options , ", " ) )
}
case "cached" , "delegated" :
// The discarded ops are OS X specific volume options
// introduced in a recent Docker version.
// They have no meaning on Linux, so here we silently
// drop them. This matches Docker's behavior (the options
// are intended to be always safe to use, even not on OS
// X).
continue
2018-04-19 03:00:15 +08:00
default :
2019-06-29 04:07:38 +08:00
return nil , errors . Errorf ( "invalid option type %q" , opt )
2018-04-19 03:00:15 +08:00
}
2019-06-29 04:07:38 +08:00
finalOpts = append ( finalOpts , opt )
2018-04-19 03:00:15 +08:00
}
2019-06-29 04:07:38 +08:00
return finalOpts , nil
2018-04-19 03:00:15 +08:00
}
// 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 {
2020-04-04 04:34:43 +08:00
return errors . Errorf ( "bad format for add-host: %q" , val )
2018-04-19 03:00:15 +08:00
}
if _ , err := validateIPAddress ( arr [ 1 ] ) ; err != nil {
2020-04-04 04:34:43 +08:00
return errors . Errorf ( "invalid IP address in add-host: %q" , arr [ 1 ] )
2018-04-19 03:00:15 +08:00
}
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
}
2020-04-04 04:34:43 +08:00
return "" , errors . Errorf ( "%s is not an ip address" , val )
2018-04-19 03:00:15 +08:00
}
// SystemContextFromOptions returns a SystemContext populated with values
// per the input parameters provided by the caller for the use in authentication.
2019-01-22 23:35:52 +08:00
func SystemContextFromOptions ( c * cobra . Command ) ( * types . SystemContext , error ) {
certDir , err := c . Flags ( ) . GetString ( "cert-dir" )
if err != nil {
certDir = ""
}
2018-04-19 03:00:15 +08:00
ctx := & types . SystemContext {
2019-01-22 23:35:52 +08:00
DockerCertPath : certDir ,
2018-04-19 03:00:15 +08:00
}
2019-01-22 23:35:52 +08:00
tlsVerify , err := c . Flags ( ) . GetBool ( "tls-verify" )
if err == nil && c . Flag ( "tls-verify" ) . Changed {
2019-02-20 23:25:24 +08:00
ctx . DockerInsecureSkipTLSVerify = types . NewOptionalBool ( ! tlsVerify )
ctx . OCIInsecureSkipTLSVerify = ! tlsVerify
ctx . DockerDaemonInsecureSkipTLSVerify = ! tlsVerify
2018-04-19 03:00:15 +08:00
}
2019-01-22 23:35:52 +08:00
creds , err := c . Flags ( ) . GetString ( "creds" )
if err == nil && c . Flag ( "creds" ) . Changed {
2018-04-19 03:00:15 +08:00
var err error
2019-01-22 23:35:52 +08:00
ctx . DockerAuthConfig , err = getDockerAuth ( creds )
2018-04-19 03:00:15 +08:00
if err != nil {
return nil , err
}
}
2019-01-22 23:35:52 +08:00
sigPolicy , err := c . Flags ( ) . GetString ( "signature-policy" )
if err == nil && c . Flag ( "signature-policy" ) . Changed {
ctx . SignaturePolicyPath = sigPolicy
2018-04-19 03:00:15 +08:00
}
2019-01-22 23:35:52 +08:00
authfile , err := c . Flags ( ) . GetString ( "authfile" )
2019-04-10 03:57:12 +08:00
if err == nil {
ctx . AuthFilePath = getAuthFile ( authfile )
2018-04-19 03:00:15 +08:00
}
2019-01-22 23:35:52 +08:00
regConf , err := c . Flags ( ) . GetString ( "registries-conf" )
if err == nil && c . Flag ( "registries-conf" ) . Changed {
ctx . SystemRegistriesConfPath = regConf
2018-04-19 03:00:15 +08:00
}
2019-01-22 23:35:52 +08:00
regConfDir , err := c . Flags ( ) . GetString ( "registries-conf-dir" )
if err == nil && c . Flag ( "registries-conf-dir" ) . Changed {
ctx . RegistriesDirPath = regConfDir
2018-04-19 03:00:15 +08:00
}
2018-06-25 16:56:52 +08:00
ctx . DockerRegistryUserAgent = fmt . Sprintf ( "Buildah/%s" , buildah . Version )
2021-01-05 01:01:23 +08:00
if c . Flag ( "os" ) != nil && c . Flag ( "os" ) . Changed {
if os , err := c . Flags ( ) . GetString ( "os" ) ; err == nil {
ctx . OSChoice = os
}
2019-09-11 06:21:07 +08:00
}
2021-01-05 01:01:23 +08:00
if c . Flag ( "arch" ) != nil && c . Flag ( "arch" ) . Changed {
if arch , err := c . Flags ( ) . GetString ( "arch" ) ; err == nil {
ctx . ArchitectureChoice = arch
}
2020-12-23 05:12:38 +08:00
}
2021-01-05 01:01:23 +08:00
if c . Flag ( "variant" ) != nil && c . Flag ( "variant" ) . Changed {
if variant , err := c . Flags ( ) . GetString ( "variant" ) ; err == nil {
ctx . VariantChoice = variant
2020-12-23 05:12:38 +08:00
}
2021-01-05 01:01:23 +08:00
}
if c . Flag ( "platform" ) != nil && c . Flag ( "platform" ) . Changed {
if platform , err := c . Flags ( ) . GetString ( "platform" ) ; err == nil {
os , arch , variant , err := parsePlatform ( 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
2020-12-23 05:12:38 +08:00
}
}
2021-01-07 03:47:09 +08:00
ctx . BigFilesTemporaryDir = GetTempDir ( )
2018-04-19 03:00:15 +08:00
return ctx , nil
}
2019-04-10 03:57:12 +08:00
func getAuthFile ( authfile string ) string {
if authfile != "" {
return authfile
}
return os . Getenv ( "REGISTRY_AUTH_FILE" )
}
2020-01-20 19:31:09 +08:00
// PlatformFromOptions parses the operating system (os) and architecture (arch)
// from the provided command line options.
func PlatformFromOptions ( c * cobra . Command ) ( os , arch string , err error ) {
2021-01-05 01:01:23 +08:00
if c . Flag ( "os" ) . Changed {
if selectedOS , err := c . Flags ( ) . GetString ( "os" ) ; err == nil {
os = selectedOS
}
2020-01-20 19:31:09 +08:00
}
2021-01-05 01:01:23 +08:00
if c . Flag ( "arch" ) . Changed {
if selectedArch , err := c . Flags ( ) . GetString ( "arch" ) ; err == nil {
arch = selectedArch
}
2020-01-20 19:31:09 +08:00
}
2021-01-05 01:01:23 +08:00
if c . Flag ( "platform" ) . Changed {
if pf , err := c . Flags ( ) . GetString ( "platform" ) ; err == nil {
selectedOS , selectedArch , _ , err := parsePlatform ( pf )
if err != nil {
return "" , "" , errors . Wrap ( err , "unable to parse platform" )
}
arch = selectedArch
os = selectedOS
2020-01-20 19:31:09 +08:00
}
}
return os , arch , nil
}
const platformSep = "/"
// DefaultPlatform returns the standard platform for the current system
func DefaultPlatform ( ) string {
return runtime . GOOS + platformSep + runtime . GOARCH
}
2020-12-23 05:12:38 +08:00
func parsePlatform ( platform string ) ( os , arch , variant string , err error ) {
2020-01-20 19:31:09 +08:00
split := strings . Split ( platform , platformSep )
2020-12-23 05:12:38 +08:00
if len ( split ) < 2 {
return "" , "" , "" , errors . Errorf ( "invalid platform syntax for %q (use OS/ARCH)" , platform )
}
os = split [ 0 ]
arch = split [ 1 ]
if len ( split ) == 3 {
variant = split [ 2 ]
2020-01-20 19:31:09 +08:00
}
2020-12-23 05:12:38 +08:00
return
2020-01-20 19:31:09 +08:00
}
2018-04-19 03:00:15 +08:00
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 ]
}
func getDockerAuth ( creds string ) ( * types . DockerAuthConfig , error ) {
username , password := parseCreds ( creds )
if username == "" {
fmt . Print ( "Username: " )
fmt . Scanln ( & username )
}
if password == "" {
fmt . Print ( "Password: " )
termPassword , err := terminal . 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
}
2018-06-07 12:45:17 +08:00
2018-06-16 00:28:03 +08:00
// IDMappingOptions parses the build options related to user namespaces and ID mapping.
2019-03-08 22:00:20 +08:00
func IDMappingOptions ( c * cobra . Command , isolation buildah . Isolation ) ( usernsOptions buildah . NamespaceOptions , idmapOptions * buildah . IDMappingOptions , err error ) {
2019-01-22 23:35:52 +08:00
user := c . Flag ( "userns-uid-map-user" ) . Value . String ( )
group := c . Flag ( "userns-gid-map-group" ) . Value . String ( )
2018-06-07 12:45:17 +08:00
// 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
}
2019-01-22 23:35:52 +08:00
globalOptions := c . PersistentFlags ( )
2018-06-07 12:45:17 +08:00
// 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.
2018-06-15 10:03:55 +08:00
var spec [ ] string
2019-01-22 23:35:52 +08:00
if globalOptions . Lookup ( option ) != nil && globalOptions . Lookup ( option ) . Changed {
spec , _ = globalOptions . GetStringSlice ( option )
2018-06-15 10:03:55 +08:00
}
2019-01-22 23:35:52 +08:00
if c . Flag ( option ) . Changed {
spec , _ = c . Flags ( ) . GetStringSlice ( option )
2018-06-15 10:03:55 +08:00
}
idmap , err := parseIDMap ( spec )
2018-06-07 12:45:17 +08:00
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
}
2019-03-08 22:00:20 +08:00
2018-06-07 12:45:17 +08:00
// By default, having mappings configured means we use a user
// namespace. Otherwise, we don't.
usernsOption := buildah . NamespaceOption {
Name : string ( specs . UserNamespace ) ,
2019-03-20 04:08:02 +08:00
Host : len ( uidmap ) == 0 && len ( gidmap ) == 0 ,
2018-06-07 12:45:17 +08:00
}
// If the user specifically requested that we either use or don't use
// user namespaces, override that default.
2019-01-22 23:35:52 +08:00
if c . Flag ( "userns" ) . Changed {
how := c . Flag ( "userns" ) . Value . String ( )
2018-06-07 12:45:17 +08:00
switch how {
2020-08-04 22:13:17 +08:00
case "" , "container" , "private" :
2018-06-07 12:45:17 +08:00
usernsOption . Host = false
case "host" :
usernsOption . Host = true
default :
2020-09-30 22:13:02 +08:00
how = strings . TrimPrefix ( how , "ns:" )
2018-06-07 12:45:17 +08:00
if _ , err := os . Stat ( how ) ; err != nil {
return nil , nil , errors . Wrapf ( err , "error checking for %s namespace at %q" , string ( specs . UserNamespace ) , how )
}
logrus . Debugf ( "setting %q namespace to %q" , string ( specs . UserNamespace ) , how )
usernsOption . Path = how
}
}
usernsOptions = buildah . NamespaceOptions { usernsOption }
2019-01-22 23:35:52 +08:00
usernetwork := c . Flags ( ) . Lookup ( "network" )
2020-08-04 22:13:17 +08:00
if usernetwork != nil && ! usernetwork . Changed {
2018-06-07 12:45:17 +08:00
usernsOptions = append ( usernsOptions , buildah . NamespaceOption {
Name : string ( specs . NetworkNamespace ) ,
Host : usernsOption . Host ,
} )
}
// 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 , & buildah . 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 {
2020-04-04 04:34:43 +08:00
return nil , errors . Errorf ( "mapping %q is not in the form containerid:hostid:size[,...]" , s )
2018-06-07 12:45:17 +08:00
}
for len ( args ) >= 3 {
cid , err := strconv . ParseUint ( args [ 0 ] , 10 , 32 )
if err != nil {
2020-04-04 04:34:43 +08:00
return nil , errors . Wrapf ( err , "error parsing container ID %q from mapping %q as a number" , args [ 0 ] , s )
2018-06-07 12:45:17 +08:00
}
hostid , err := strconv . ParseUint ( args [ 1 ] , 10 , 32 )
if err != nil {
2020-04-04 04:34:43 +08:00
return nil , errors . Wrapf ( err , "error parsing host ID %q from mapping %q as a number" , args [ 1 ] , s )
2018-06-07 12:45:17 +08:00
}
size , err := strconv . ParseUint ( args [ 2 ] , 10 , 32 )
if err != nil {
2020-04-04 04:34:43 +08:00
return nil , errors . Wrapf ( err , "error parsing %q from mapping %q as a number" , args [ 2 ] , s )
2018-06-07 12:45:17 +08:00
}
m = append ( m , [ 3 ] uint32 { uint32 ( cid ) , uint32 ( hostid ) , uint32 ( size ) } )
args = args [ 3 : ]
}
}
return m , nil
}
2018-06-16 00:28:03 +08:00
// NamespaceOptions parses the build options for all namespaces except for user namespace.
2019-01-22 23:35:52 +08:00
func NamespaceOptions ( c * cobra . Command ) ( namespaceOptions buildah . NamespaceOptions , networkPolicy buildah . NetworkConfigurationPolicy , err error ) {
2018-06-07 12:45:17 +08:00
options := make ( buildah . NamespaceOptions , 0 , 7 )
policy := buildah . NetworkDefault
2020-08-04 22:13:17 +08:00
for _ , what := range [ ] string { string ( specs . IPCNamespace ) , "network" , string ( specs . PIDNamespace ) , string ( specs . UTSNamespace ) } {
2019-01-22 23:35:52 +08:00
if c . Flags ( ) . Lookup ( what ) != nil && c . Flag ( what ) . Changed {
how := c . Flag ( what ) . Value . String ( )
2018-06-07 12:45:17 +08:00
switch what {
2020-08-04 22:13:17 +08:00
case "network" :
2018-06-07 12:45:17 +08:00
what = string ( specs . NetworkNamespace )
}
switch how {
2020-08-04 22:13:17 +08:00
case "" , "container" , "private" :
2018-06-07 12:45:17 +08:00
logrus . Debugf ( "setting %q namespace to %q" , what , "" )
options . AddOrReplace ( buildah . NamespaceOption {
Name : what ,
} )
case "host" :
logrus . Debugf ( "setting %q namespace to host" , what )
options . AddOrReplace ( buildah . NamespaceOption {
Name : what ,
Host : true ,
} )
default :
2019-07-16 22:47:25 +08:00
if what == string ( specs . NetworkNamespace ) {
2018-06-07 12:45:17 +08:00
if how == "none" {
options . AddOrReplace ( buildah . NamespaceOption {
Name : what ,
} )
policy = buildah . NetworkDisabled
logrus . Debugf ( "setting network to disabled" )
break
}
}
2020-09-30 22:13:02 +08:00
how = strings . TrimPrefix ( how , "ns:" )
2018-06-07 12:45:17 +08:00
if _ , err := os . Stat ( how ) ; err != nil {
2020-12-02 23:03:07 +08:00
return nil , buildah . NetworkDefault , errors . Wrapf ( err , "error checking for %s namespace" , what )
2018-06-07 12:45:17 +08:00
}
2020-12-02 23:03:07 +08:00
policy = buildah . NetworkEnabled
2018-06-07 12:45:17 +08:00
logrus . Debugf ( "setting %q namespace to %q" , what , how )
options . AddOrReplace ( buildah . NamespaceOption {
Name : what ,
Path : how ,
} )
}
}
}
return options , policy , nil
}
2018-05-12 01:00:14 +08:00
2018-05-12 01:08:18 +08:00
func defaultIsolation ( ) ( buildah . Isolation , error ) {
isolation , isSet := os . LookupEnv ( "BUILDAH_ISOLATION" )
if isSet {
2018-10-04 14:19:12 +08:00
switch strings . ToLower ( isolation ) {
case "oci" :
2018-05-12 01:08:18 +08:00
return buildah . IsolationOCI , nil
2018-10-04 14:19:12 +08:00
case "rootless" :
2018-07-19 02:59:20 +08:00
return buildah . IsolationOCIRootless , nil
2018-10-04 14:19:12 +08:00
case "chroot" :
2018-05-12 01:08:18 +08:00
return buildah . IsolationChroot , nil
2018-10-04 14:19:12 +08:00
default :
return 0 , errors . Errorf ( "unrecognized $BUILDAH_ISOLATION value %q" , isolation )
2018-05-12 01:08:18 +08:00
}
2018-05-12 01:00:14 +08:00
}
2020-07-07 22:15:40 +08:00
if unshare . IsRootless ( ) {
return buildah . IsolationOCIRootless , nil
}
2018-05-12 01:08:18 +08:00
return buildah . IsolationDefault , nil
2018-05-12 01:00:14 +08:00
}
// IsolationOption parses the --isolation flag.
2020-02-08 01:54:18 +08:00
func IsolationOption ( isolation string ) ( buildah . Isolation , error ) {
2019-01-22 23:35:52 +08:00
if isolation != "" {
switch strings . ToLower ( isolation ) {
2018-10-04 14:19:12 +08:00
case "oci" :
2018-05-12 01:00:14 +08:00
return buildah . IsolationOCI , nil
2018-10-04 14:19:12 +08:00
case "rootless" :
2018-07-19 02:59:20 +08:00
return buildah . IsolationOCIRootless , nil
2018-10-04 14:19:12 +08:00
case "chroot" :
2018-05-12 01:08:18 +08:00
return buildah . IsolationChroot , nil
2018-10-04 14:19:12 +08:00
default :
2019-01-22 23:35:52 +08:00
return 0 , errors . Errorf ( "unrecognized isolation type %q" , isolation )
2018-05-12 01:00:14 +08:00
}
}
2018-05-12 01:08:18 +08:00
return defaultIsolation ( )
2018-05-12 01:00:14 +08:00
}
2019-04-10 03:57:12 +08:00
2019-09-07 03:07:18 +08:00
// Device parses device mapping string to a src, dest & permissions string
2020-12-22 00:19:56 +08:00
// Valid values for device look like:
2019-09-07 03:07:18 +08:00
// '/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 ] ) {
2020-04-04 04:34:43 +08:00
return "" , "" , "" , errors . Errorf ( "invalid device mode: %s" , arr [ 2 ] )
2019-09-07 03:07:18 +08:00
}
permissions = arr [ 2 ]
fallthrough
case 2 :
if isValidDeviceMode ( arr [ 1 ] ) {
permissions = arr [ 1 ]
} else {
if len ( arr [ 1 ] ) == 0 || arr [ 1 ] [ 0 ] != '/' {
2020-04-04 04:34:43 +08:00
return "" , "" , "" , errors . Errorf ( "invalid device mode: %s" , arr [ 1 ] )
2019-09-07 03:07:18 +08:00
}
dst = arr [ 1 ]
}
fallthrough
case 1 :
if len ( arr [ 0 ] ) > 0 {
src = arr [ 0 ]
break
}
fallthrough
default :
2020-04-04 04:34:43 +08:00
return "" , "" , "" , errors . Errorf ( "invalid device specification: %s" , device )
2019-09-07 03:07:18 +08:00
}
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
}
2019-12-18 00:01:52 +08:00
func GetTempDir ( ) string {
if tmpdir , ok := os . LookupEnv ( "TMPDIR" ) ; ok {
return tmpdir
}
return "/var/tmp"
}