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 }