github.com/canonical/ubuntu-image@v0.0.0-20240430122802-2202fe98b290/internal/statemachine/snap_states.go (about)

     1  package statemachine
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"github.com/snapcore/snapd/image"
    10  	"github.com/snapcore/snapd/interfaces/builtin"
    11  	"github.com/snapcore/snapd/seed/seedwriter"
    12  	"github.com/snapcore/snapd/snap"
    13  )
    14  
    15  var prepareImageState = stateFunc{"prepare_image", (*StateMachine).prepareImage}
    16  
    17  // Prepare the image
    18  func (stateMachine *StateMachine) prepareImage() error {
    19  	snapStateMachine := stateMachine.parent.(*SnapStateMachine)
    20  
    21  	imageOpts := &image.Options{
    22  		ModelFile:                 snapStateMachine.Args.ModelAssertion,
    23  		Preseed:                   snapStateMachine.Opts.Preseed,
    24  		PreseedSignKey:            snapStateMachine.Opts.PreseedSignKey,
    25  		AppArmorKernelFeaturesDir: snapStateMachine.Opts.AppArmorKernelFeaturesDir,
    26  		SysfsOverlay:              snapStateMachine.Opts.SysfsOverlay,
    27  		SeedManifestPath:          filepath.Join(stateMachine.commonFlags.OutputDir, "seed.manifest"),
    28  		PrepareDir:                snapStateMachine.tempDirs.unpack,
    29  		Channel:                   snapStateMachine.commonFlags.Channel,
    30  		Customizations:            snapStateMachine.imageOptsCustomizations(),
    31  	}
    32  
    33  	var err error
    34  	imageOpts.Snaps, imageOpts.SnapChannels, err = parseSnapsAndChannels(
    35  		snapStateMachine.Opts.Snaps)
    36  	if err != nil {
    37  		return err
    38  	}
    39  
    40  	imageOpts.SeedManifest, err = snapStateMachine.imageOptsSeedManifest()
    41  	if err != nil {
    42  		return fmt.Errorf("Error preparing image: %s", err.Error())
    43  	}
    44  
    45  	// plug/slot sanitization needed by provider handling
    46  	snap.SanitizePlugsSlots = builtin.SanitizePlugsSlots
    47  
    48  	// image.Prepare automatically has some output that we only want for
    49  	// verbose or greater logging
    50  	if !stateMachine.commonFlags.Debug && !stateMachine.commonFlags.Verbose {
    51  		oldImageStdout := image.Stdout
    52  		image.Stdout = io.Discard
    53  		defer func() {
    54  			image.Stdout = oldImageStdout
    55  		}()
    56  	}
    57  
    58  	if err := imagePrepare(imageOpts); err != nil {
    59  		return fmt.Errorf("Error preparing image: %s", err.Error())
    60  	}
    61  
    62  	snapStateMachine.YamlFilePath = filepath.Join(stateMachine.tempDirs.unpack, "gadget", gadgetYamlPathInTree)
    63  
    64  	return nil
    65  }
    66  
    67  // imageOptsSeedManifest sets up the pre-provided manifest if revisions are passed
    68  func (snapStateMachine *SnapStateMachine) imageOptsSeedManifest() (*seedwriter.Manifest, error) {
    69  	if len(snapStateMachine.Opts.Revisions) == 0 {
    70  		return nil, nil
    71  	}
    72  	seedManifest := seedwriter.NewManifest()
    73  	for snapName, snapRev := range snapStateMachine.Opts.Revisions {
    74  		fmt.Printf("WARNING: revision %d for snap %s may not be the latest available version!\n", snapRev, snapName)
    75  		err := seedManifest.SetAllowedSnapRevision(snapName, snap.R(snapRev))
    76  		if err != nil {
    77  			return nil, fmt.Errorf("error dealing with snap revision %s: %w", snapName, err)
    78  		}
    79  	}
    80  
    81  	return seedManifest, nil
    82  }
    83  
    84  // imageOptsCustomizations prepares the Customizations options to give to image.Prepare
    85  func (snapStateMachine *SnapStateMachine) imageOptsCustomizations() image.Customizations {
    86  	customizations := image.Customizations{
    87  		CloudInitUserData: snapStateMachine.Opts.CloudInit,
    88  		Validation:        snapStateMachine.commonFlags.Validation,
    89  	}
    90  	if snapStateMachine.Opts.DisableConsoleConf {
    91  		customizations.ConsoleConf = "disabled"
    92  	}
    93  	if snapStateMachine.Opts.FactoryImage {
    94  		customizations.BootFlags = append(customizations.BootFlags, "factory")
    95  	}
    96  
    97  	return customizations
    98  }
    99  
   100  var populateSnapRootfsContentsState = stateFunc{"populate_rootfs_contents", (*StateMachine).populateSnapRootfsContents}
   101  
   102  // populateSnapRootfsContents populates the rootfs
   103  func (stateMachine *StateMachine) populateSnapRootfsContents() error {
   104  	var src, dst string
   105  	if stateMachine.IsSeeded {
   106  		// For now, since we only create the system-seed partition for
   107  		// uc20+ images, we hard-code to use this path for the rootfs
   108  		// seed population.  In the future we might want to consider
   109  		// populating other partitions from `snap prepare-image` output
   110  		// as well, so looking into directories like system-data/ etc.
   111  		src = filepath.Join(stateMachine.tempDirs.unpack, "system-seed")
   112  		dst = stateMachine.tempDirs.rootfs
   113  	} else {
   114  		src = filepath.Join(stateMachine.tempDirs.unpack, "image")
   115  		dst = filepath.Join(stateMachine.tempDirs.rootfs, "system-data")
   116  		err := osMkdirAll(filepath.Join(dst, "boot"), 0755)
   117  		if err != nil && !os.IsExist(err) {
   118  			return fmt.Errorf("Error creating boot dir: %s", err.Error())
   119  		}
   120  	}
   121  
   122  	// recursively copy the src to dst, skipping /boot for non-seeded images
   123  	files, err := osReadDir(src)
   124  	if err != nil {
   125  		return fmt.Errorf("Error reading unpack dir: %s", err.Error())
   126  	}
   127  	for _, srcFile := range files {
   128  		if !stateMachine.IsSeeded && srcFile.Name() == "boot" {
   129  			continue
   130  		}
   131  		srcFileName := filepath.Join(src, srcFile.Name())
   132  		dstFileName := filepath.Join(dst, srcFile.Name())
   133  		if err := osRename(srcFileName, dstFileName); err != nil {
   134  			return fmt.Errorf("Error moving rootfs: %s", err.Error())
   135  		}
   136  	}
   137  
   138  	return nil
   139  }
   140  
   141  var generateSnapManifestState = stateFunc{"generate_snap_manifest", (*StateMachine).generateSnapManifest}
   142  
   143  // Generate the manifest
   144  func (stateMachine *StateMachine) generateSnapManifest() error {
   145  	// We could use snapd's seed.Open() to generate the manifest here, but
   146  	// actually it doesn't make things much easier than doing it manually -
   147  	// like we did in the past. So let's just go with this.
   148  
   149  	outputPath := filepath.Join(stateMachine.commonFlags.OutputDir, "snaps.manifest")
   150  	snapsDir := filepath.Join(stateMachine.tempDirs.rootfs, "system-data", "var", "lib", "snapd", "snaps")
   151  	return WriteSnapManifest(snapsDir, outputPath)
   152  }