github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/imageengine/buildah/from.go (about) 1 // Copyright © 2022 Alibaba Group Holding Ltd. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package buildah 16 17 import ( 18 "fmt" 19 "io" 20 "os" 21 "strings" 22 23 "github.com/containers/buildah" 24 "github.com/containers/buildah/define" 25 buildahcli "github.com/containers/buildah/pkg/cli" 26 "github.com/containers/buildah/pkg/parse" 27 "github.com/containers/common/pkg/config" 28 encconfig "github.com/containers/ocicrypt/config" 29 30 "github.com/pkg/errors" 31 32 "github.com/sealerio/sealer/pkg/define/options" 33 ) 34 35 type fromFlagsWrapper struct { 36 *buildahcli.FromAndBudResults 37 *buildahcli.UserNSResults 38 *buildahcli.NameSpaceResults 39 } 40 41 // createContainerFromImage create a working container. This function is copied from 42 // "buildah from". This function takes args([]string{"$image"}), and create a working container 43 // based on $image, this will generate an empty dictionary, not a real rootfs. And this container is a fake container. 44 func (engine *Engine) createContainerFromImage(opts *options.FromOptions) (string, error) { 45 defaultContainerConfig, err := config.Default() 46 if err != nil { 47 return "", errors.Wrapf(err, "failed to get container config") 48 } 49 50 if len(opts.Image) == 0 { 51 return "", errors.Errorf("an image name (or \"scratch\") must be specified") 52 } 53 54 // TODO be aware of this, maybe this will incur platform problem. 55 systemCxt := engine.SystemContext() 56 57 // TODO we do not support from remote currently 58 // which is to make the policy pull-if-missing 59 pullPolicy := define.PullNever 60 61 store := engine.ImageStore() 62 63 commonOpts, err := parse.CommonBuildOptions(engine.Command) 64 if err != nil { 65 return "", err 66 } 67 68 isolation, err := defaultIsolationOption() 69 if err != nil { 70 return "", err 71 } 72 73 namespaceOptions, networkPolicy := defaultNamespaceOptions() 74 75 usernsOption, idmappingOptions, err := parse.IDMappingOptions(engine.Command, isolation) 76 if err != nil { 77 return "", errors.Wrapf(err, "error parsing ID mapping options") 78 } 79 namespaceOptions.AddOrReplace(usernsOption...) 80 81 // hardcode format here, user do not concern about this. 82 format, err := getImageType(define.OCI) 83 if err != nil { 84 return "", err 85 } 86 87 capabilities, err := defaultContainerConfig.Capabilities("", []string{}, []string{}) 88 if err != nil { 89 return "", err 90 } 91 92 commonOpts.Ulimit = append(defaultContainerConfig.Containers.DefaultUlimits, commonOpts.Ulimit...) 93 94 options := buildah.BuilderOptions{ 95 FromImage: opts.Image, 96 Container: "", 97 ContainerSuffix: "", 98 PullPolicy: pullPolicy, 99 SystemContext: systemCxt, 100 DefaultMountsFilePath: "", 101 Isolation: isolation, 102 NamespaceOptions: namespaceOptions, 103 ConfigureNetwork: networkPolicy, 104 CNIPluginPath: "", 105 CNIConfigDir: "", 106 IDMappingOptions: idmappingOptions, 107 Capabilities: capabilities, 108 CommonBuildOpts: commonOpts, 109 Format: format, 110 DefaultEnv: defaultContainerConfig.GetDefaultEnv(), 111 MaxPullRetries: maxPullPushRetries, 112 PullRetryDelay: pullPushRetryDelay, 113 OciDecryptConfig: &encconfig.DecryptConfig{}, 114 } 115 116 if !opts.Quiet { 117 options.ReportWriter = os.Stderr 118 } 119 120 builder, err := buildah.NewBuilder(getContext(), store, options) 121 if err != nil { 122 return "", err 123 } 124 125 if err := onBuild(builder, opts.Quiet); err != nil { 126 return "", err 127 } 128 129 return builder.ContainerID, builder.Save() 130 } 131 132 func (engine *Engine) CreateContainer(opts *options.FromOptions) (string, error) { 133 wrapper := &fromFlagsWrapper{ 134 FromAndBudResults: &buildahcli.FromAndBudResults{}, 135 UserNSResults: &buildahcli.UserNSResults{}, 136 NameSpaceResults: &buildahcli.NameSpaceResults{}, 137 } 138 139 flags := engine.Flags() 140 fromAndBudFlags, err := buildahcli.GetFromAndBudFlags(wrapper.FromAndBudResults, wrapper.UserNSResults, wrapper.NameSpaceResults) 141 if err != nil { 142 return "", err 143 } 144 145 flags.AddFlagSet(&fromAndBudFlags) 146 147 err = engine.migrateFlags2BuildahFrom(opts) 148 if err != nil { 149 return "", err 150 } 151 152 return engine.createContainerFromImage(opts) 153 } 154 155 // CreateWorkingContainer will make a workingContainer with rootfs under /var/lib/containers/storage 156 // And then link rootfs to the DestDir 157 // And remember to call RemoveContainer to remove the link and remove the container(umount rootfs) manually. 158 func (engine *Engine) CreateWorkingContainer(opts *options.BuildRootfsOptions) (containerID string, err error) { 159 // TODO clean environment when it fails 160 cid, err := engine.CreateContainer(&options.FromOptions{ 161 Image: opts.ImageNameOrID, 162 Quiet: false, 163 }) 164 if err != nil { 165 return "", err 166 } 167 168 mounts, err := engine.Mount(&options.MountOptions{Containers: []string{cid}}) 169 if err != nil { 170 return "", err 171 } 172 173 // remove destination dir if it exists, otherwise the Symlink will fail. 174 if _, err = os.Stat(opts.DestDir); err == nil { 175 return "", fmt.Errorf("destination directionay %s exists, you should remove it first", opts.DestDir) 176 } 177 178 mountPoint := mounts[0].MountPoint 179 return cid, os.Symlink(mountPoint, opts.DestDir) 180 } 181 182 func (engine *Engine) migrateFlags2BuildahFrom(opts *options.FromOptions) error { 183 return nil 184 } 185 186 func onBuild(builder *buildah.Builder, quiet bool) error { 187 ctr := 0 188 for _, onBuildSpec := range builder.OnBuild() { 189 ctr = ctr + 1 190 commands := strings.Split(onBuildSpec, " ") 191 command := strings.ToUpper(commands[0]) 192 args := commands[1:] 193 if !quiet { 194 fmt.Fprintf(os.Stderr, "STEP %d: %s\n", ctr, onBuildSpec) 195 } 196 switch command { 197 case "ADD": 198 case "COPY": 199 dest := "" 200 srcs := []string{} 201 size := len(args) 202 if size > 1 { 203 dest = args[size-1] 204 srcs = args[:size-1] 205 } 206 if err := builder.Add(dest, command == "ADD", buildah.AddAndCopyOptions{}, srcs...); err != nil { 207 return err 208 } 209 case "ANNOTATION": 210 annotation := strings.SplitN(args[0], "=", 2) 211 if len(annotation) > 1 { 212 builder.SetAnnotation(annotation[0], annotation[1]) 213 } else { 214 builder.UnsetAnnotation(annotation[0]) 215 } 216 case "CMD": 217 builder.SetCmd(args) 218 case "ENV": 219 env := strings.SplitN(args[0], "=", 2) 220 if len(env) > 1 { 221 builder.SetEnv(env[0], env[1]) 222 } else { 223 builder.UnsetEnv(env[0]) 224 } 225 case "ENTRYPOINT": 226 builder.SetEntrypoint(args) 227 case "EXPOSE": 228 builder.SetPort(strings.Join(args, " ")) 229 case "HOSTNAME": 230 builder.SetHostname(strings.Join(args, " ")) 231 case "LABEL": 232 label := strings.SplitN(args[0], "=", 2) 233 if len(label) > 1 { 234 builder.SetLabel(label[0], label[1]) 235 } else { 236 builder.UnsetLabel(label[0]) 237 } 238 case "MAINTAINER": 239 builder.SetMaintainer(strings.Join(args, " ")) 240 case "ONBUILD": 241 builder.SetOnBuild(strings.Join(args, " ")) 242 case "RUN": 243 var stdout io.Writer 244 if quiet { 245 stdout = io.Discard 246 } 247 if err := builder.Run(args, buildah.RunOptions{Stdout: stdout}); err != nil { 248 return err 249 } 250 case "SHELL": 251 builder.SetShell(args) 252 case "STOPSIGNAL": 253 builder.SetStopSignal(strings.Join(args, " ")) 254 case "USER": 255 builder.SetUser(strings.Join(args, " ")) 256 case "VOLUME": 257 builder.AddVolume(strings.Join(args, " ")) 258 case "WORKINGDIR": 259 builder.SetWorkDir(strings.Join(args, " ")) 260 default: 261 return errors.Errorf("illegal command input %q; ignored", command) 262 } 263 } 264 builder.ClearOnBuild() 265 return nil 266 }