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  }