github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/cmd/umoci/raw-runtime-config.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016-2020 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 main
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"os"
    24  
    25  	"github.com/apex/log"
    26  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    27  	"github.com/opencontainers/umoci"
    28  	"github.com/opencontainers/umoci/oci/cas/dir"
    29  	"github.com/opencontainers/umoci/oci/casext"
    30  	"github.com/opencontainers/umoci/oci/layer"
    31  	"github.com/pkg/errors"
    32  	"github.com/urfave/cli"
    33  )
    34  
    35  var rawConfigCommand = uxRemap(cli.Command{
    36  	Name:    "runtime-config",
    37  	Aliases: []string{"config"},
    38  	Usage:   "generates an OCI runtime configuration for an image",
    39  	ArgsUsage: `--image <image-path>[:<tag>] [--rootfs <rootfs>] <config.json>
    40  
    41  Where "<image-path>" is the path to the OCI image, "<tag>" is the name of the
    42  tagged image to unpack (if not specified, defaults to "latest"), "<rootfs>" is
    43  a rootfs to use as a supplementary "source of truth" for certain generation
    44  operations and "<config.json>" is the destination to write the runtime
    45  configuration to.
    46  
    47  Note that the results of this may not agree with umoci-unpack(1) because the
    48  --rootfs flag affects how certain properties are interpreted.`,
    49  
    50  	// unpack reads manifest information.
    51  	Category: "image",
    52  
    53  	Flags: []cli.Flag{
    54  		cli.StringFlag{
    55  			Name:  "rootfs",
    56  			Usage: "path to secondary source of truth (root filesystem)",
    57  		},
    58  	},
    59  
    60  	Action: rawConfig,
    61  
    62  	Before: func(ctx *cli.Context) error {
    63  		if ctx.NArg() != 1 {
    64  			return errors.Errorf("invalid number of positional arguments: expected <config.json>")
    65  		}
    66  		if ctx.Args().First() == "" {
    67  			return errors.Errorf("config.json path cannot be empty")
    68  		}
    69  		ctx.App.Metadata["config"] = ctx.Args().First()
    70  		return nil
    71  	},
    72  })
    73  
    74  func rawConfig(ctx *cli.Context) error {
    75  	imagePath := ctx.App.Metadata["--image-path"].(string)
    76  	fromName := ctx.App.Metadata["--image-tag"].(string)
    77  	configPath := ctx.App.Metadata["config"].(string)
    78  
    79  	var meta umoci.Meta
    80  	meta.Version = umoci.MetaVersion
    81  
    82  	// Parse and set up the mapping options.
    83  	err := umoci.ParseIdmapOptions(&meta, ctx)
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	// Get a reference to the CAS.
    89  	engine, err := dir.Open(imagePath)
    90  	if err != nil {
    91  		return errors.Wrap(err, "open CAS")
    92  	}
    93  	engineExt := casext.NewEngine(engine)
    94  	defer engine.Close()
    95  
    96  	fromDescriptorPaths, err := engineExt.ResolveReference(context.Background(), fromName)
    97  	if err != nil {
    98  		return errors.Wrap(err, "get descriptor")
    99  	}
   100  	if len(fromDescriptorPaths) == 0 {
   101  		return errors.Errorf("tag not found: %s", fromName)
   102  	}
   103  	if len(fromDescriptorPaths) != 1 {
   104  		// TODO: Handle this more nicely.
   105  		return errors.Errorf("tag is ambiguous: %s", fromName)
   106  	}
   107  	meta.From = fromDescriptorPaths[0]
   108  
   109  	manifestBlob, err := engineExt.FromDescriptor(context.Background(), meta.From.Descriptor())
   110  	if err != nil {
   111  		return errors.Wrap(err, "get manifest")
   112  	}
   113  	defer manifestBlob.Close()
   114  
   115  	if manifestBlob.Descriptor.MediaType != ispec.MediaTypeImageManifest {
   116  		return errors.Wrap(fmt.Errorf("descriptor does not point to ispec.MediaTypeImageManifest: not implemented: %s", manifestBlob.Descriptor.MediaType), "invalid --image tag")
   117  	}
   118  
   119  	// Get the manifest.
   120  	manifest, ok := manifestBlob.Data.(ispec.Manifest)
   121  	if !ok {
   122  		// Should _never_ be reached.
   123  		return errors.Errorf("[internal error] unknown manifest blob type: %s", manifestBlob.Descriptor.MediaType)
   124  	}
   125  
   126  	// Generate the configuration.
   127  	configFile, err := os.Create(configPath)
   128  	if err != nil {
   129  		return errors.Wrap(err, "opening config path")
   130  	}
   131  	defer configFile.Close()
   132  
   133  	// Write out the generated config.
   134  	log.Info("generating config.json")
   135  	if err := layer.UnpackRuntimeJSON(context.Background(), engineExt, configFile, ctx.String("rootfs"), manifest, &meta.MapOptions); err != nil {
   136  		return errors.Wrap(err, "generate config")
   137  	}
   138  	return nil
   139  }