github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/oci/config/convert/runtime.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2016, 2017, 2018 SUSE LLC. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package convert 19 20 import ( 21 "path/filepath" 22 "runtime" 23 "strings" 24 25 "github.com/apex/log" 26 igen "github.com/openSUSE/umoci/oci/config/generate" 27 "github.com/openSUSE/umoci/third_party/user" 28 ispec "github.com/opencontainers/image-spec/specs-go/v1" 29 rspec "github.com/opencontainers/runtime-spec/specs-go" 30 rgen "github.com/opencontainers/runtime-tools/generate" 31 "github.com/pkg/errors" 32 ) 33 34 // Annotations described by the OCI image-spec document (these represent fields 35 // in an image configuration that do not have a native representation in the 36 // runtime-spec). 37 const ( 38 authorAnnotation = "org.opencontainers.image.author" 39 createdAnnotation = "org.opencontainers.image.created" 40 stopSignalAnnotation = "org.opencontainers.image.stopSignal" 41 exposedPortsAnnotation = "org.opencontainers.image.exposedPorts" 42 ) 43 44 // ToRuntimeSpec converts the given OCI image configuration to a runtime 45 // configuration appropriate for use, which is templated on the default 46 // configuration specified by the OCI runtime-tools. It is equivalent to 47 // MutateRuntimeSpec("runtime-tools/generate".New(), image).Spec(). 48 func ToRuntimeSpec(rootfs string, image ispec.Image) (rspec.Spec, error) { 49 g, err := rgen.New(runtime.GOOS) 50 if err != nil { 51 return rspec.Spec{}, err 52 } 53 if err := MutateRuntimeSpec(g, rootfs, image); err != nil { 54 return rspec.Spec{}, err 55 } 56 return *g.Spec(), nil 57 } 58 59 // parseEnv splits a given environment variable (of the form name=value) into 60 // (name, value). An error is returned if there is no "=" in the line or if the 61 // name is empty. 62 func parseEnv(env string) (string, string, error) { 63 parts := strings.SplitN(env, "=", 2) 64 if len(parts) != 2 { 65 return "", "", errors.Errorf("environment variable must contain '=': %s", env) 66 } 67 68 name, value := parts[0], parts[1] 69 if name == "" { 70 return "", "", errors.Errorf("environment variable must have non-empty name: %s", env) 71 } 72 return name, value, nil 73 } 74 75 // MutateRuntimeSpec mutates a given runtime specification generator with the 76 // image configuration provided. It returns the original generator, and does 77 // not modify any fields directly (to allow for chaining). 78 func MutateRuntimeSpec(g rgen.Generator, rootfs string, image ispec.Image) error { 79 ig, err := igen.NewFromImage(image) 80 if err != nil { 81 return errors.Wrap(err, "creating image generator") 82 } 83 84 if ig.OS() != "linux" { 85 return errors.Errorf("unsupported OS: %s", image.OS) 86 } 87 88 // FIXME: We need to figure out if we're modifying an incompatible runtime spec. 89 //g.SetVersion(rspec.Version) 90 // TODO: We stopped including the OS and Architecture information in the runtime-spec. 91 // Make sure we fix that once https://github.com/opencontainers/image-spec/pull/711 92 // is resolved. 93 94 // Set verbatim fields 95 g.SetProcessTerminal(true) 96 g.SetRootPath(filepath.Base(rootfs)) 97 g.SetRootReadonly(false) 98 99 g.SetProcessCwd("/") 100 if ig.ConfigWorkingDir() != "" { 101 g.SetProcessCwd(ig.ConfigWorkingDir()) 102 } 103 104 for _, env := range ig.ConfigEnv() { 105 name, value, err := parseEnv(env) 106 if err != nil { 107 return errors.Wrap(err, "parsing image.Config.Env") 108 } 109 g.AddProcessEnv(name, value) 110 } 111 112 args := []string{} 113 args = append(args, ig.ConfigEntrypoint()...) 114 args = append(args, ig.ConfigCmd()...) 115 if len(args) > 0 { 116 g.SetProcessArgs(args) 117 } 118 119 // Set annotations fields 120 for key, value := range ig.ConfigLabels() { 121 g.AddAnnotation(key, value) 122 } 123 g.AddAnnotation(authorAnnotation, ig.Author()) 124 g.AddAnnotation(createdAnnotation, ig.Created().Format(igen.ISO8601)) 125 g.AddAnnotation(stopSignalAnnotation, image.Config.StopSignal) 126 127 // Set parsed fields 128 // Get the *actual* uid and gid of the user. If the image doesn't contain 129 // an /etc/passwd or /etc/group file then GetExecUserPath will just do a 130 // numerical parsing. 131 var passwdPath, groupPath string 132 if rootfs != "" { 133 passwdPath = filepath.Join(rootfs, "/etc/passwd") 134 groupPath = filepath.Join(rootfs, "/etc/group") 135 } 136 execUser, err := user.GetExecUserPath(ig.ConfigUser(), nil, passwdPath, groupPath) 137 if err != nil { 138 // We only log an error if were not given a rootfs, and we set execUser 139 // to the "default" (root:root). 140 if rootfs != "" { 141 return errors.Wrapf(err, "cannot parse user spec: '%s'", ig.ConfigUser()) 142 } 143 log.Warnf("could not parse user spec '%s' without a rootfs -- defaulting to root:root", ig.ConfigUser()) 144 execUser = new(user.ExecUser) 145 } 146 147 g.SetProcessUID(uint32(execUser.Uid)) 148 g.SetProcessGID(uint32(execUser.Gid)) 149 g.ClearProcessAdditionalGids() 150 151 for _, gid := range execUser.Sgids { 152 g.AddProcessAdditionalGid(uint32(gid)) 153 } 154 if execUser.Home != "" { 155 g.AddProcessEnv("HOME", execUser.Home) 156 } 157 158 // Set optional fields 159 ports := ig.ConfigExposedPortsArray() 160 g.AddAnnotation(exposedPortsAnnotation, strings.Join(ports, ",")) 161 162 for vol := range ig.ConfigVolumes() { 163 // XXX: This is _fine_ but might cause some issues in the future. 164 g.AddMount(rspec.Mount{ 165 Destination: vol, 166 Type: "tmpfs", 167 Source: "none", 168 Options: []string{"rw", "nosuid", "nodev", "noexec", "relatime"}, 169 }) 170 } 171 172 // Remove all seccomp rules. 173 g.Spec().Linux.Seccomp = nil 174 175 return nil 176 }