-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1633 port sysext command from enki (#101)
* Port sysext command from enki part of: kairos-io/kairos#1633 Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Don't use viper Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Use global "--debug" option and some minor fixes - args that weren't used - command output was not printed Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> --------- Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
- Loading branch information
1 parent
e538d26
commit 2a1a58c
Showing
3 changed files
with
170 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/gofrs/uuid" | ||
"github.com/kairos-io/kairos-sdk/sysext" | ||
sdkTypes "github.com/kairos-io/kairos-sdk/types" | ||
"github.com/kairos-io/kairos-sdk/utils" | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
// Use: "build-uki SourceImage", | ||
// Short: "Build a UKI artifact from a container image", | ||
var SysextCmd = cli.Command{ | ||
Name: "sysext", | ||
Usage: "Generate a sysextension from the last layer of the given CONTAINER", | ||
ArgsUsage: "<name> <container>", | ||
|
||
Flags: []cli.Flag{ | ||
&cli.StringFlag{ | ||
Name: "private-key", | ||
Value: "", | ||
Usage: "Private key to sign the sysext with", | ||
Required: true, | ||
}, | ||
&cli.StringFlag{ | ||
Name: "certificate", | ||
Usage: "Certificate to sign the sysext with", | ||
Required: true, | ||
}, | ||
&cli.BoolFlag{ | ||
Name: "service-load", | ||
Value: false, | ||
Usage: "Make systemctl reload the service when loading the sysext. This is useful for sysext that provide systemd service files.", | ||
}, | ||
&cli.StringFlag{ | ||
Name: "output", | ||
Usage: "Output dir", | ||
}, | ||
&cli.StringFlag{ | ||
Name: "arch", | ||
Value: "amd64", | ||
Usage: "Arch to get the image from and build the sysext for. Accepts amd64 and arm64 values.", | ||
}, | ||
}, | ||
Before: func(ctx *cli.Context) error { | ||
arch := ctx.String("arch") | ||
if arch != "amd64" && arch != "arm64" { | ||
return fmt.Errorf("unsupported architecture: %s", arch) | ||
} | ||
return nil | ||
}, | ||
Action: func(ctx *cli.Context) error { | ||
level := "warn" | ||
if ctx.Bool("debug") { | ||
level = "debug" | ||
} | ||
logger := sdkTypes.NewKairosLogger("auroraboot", level, false) | ||
args := ctx.Args() | ||
|
||
name := args.Get(0) | ||
if _, err := os.Stat(fmt.Sprintf("%s.sysext.raw", name)); err == nil { | ||
_ = os.Remove(fmt.Sprintf("%s.sysext.raw", name)) | ||
} | ||
logger.Info("π Start sysext creation") | ||
|
||
dir, err := os.MkdirTemp("", "auroraboot-sysext-") | ||
if err != nil { | ||
return fmt.Errorf("creating temp directory: %w", err) | ||
} | ||
defer func(path string) { | ||
err := os.RemoveAll(path) | ||
if err != nil { | ||
logger.Logger.Error().Str("dir", dir).Err(err).Msg("β removing dir") | ||
} | ||
}(dir) | ||
logger.Logger.Debug().Str("dir", dir).Msg("creating directory") | ||
|
||
// Get the image struct | ||
logger.Info("πΏ Getting image info") | ||
platform := fmt.Sprintf("linux/%s", ctx.String("arch")) | ||
image, err := utils.GetImage(args.Get(1), platform, nil, nil) | ||
if err != nil { | ||
logger.Logger.Error().Str("image", args.Get(1)).Err(err).Msg("β getting image") | ||
return err | ||
} | ||
// Only for sysext, confext not supported yet | ||
AllowList := regexp.MustCompile(`^usr/*|^/usr/*`) | ||
// extract the files into the temp dir | ||
logger.Info("π€ Extracting archives from image layer") | ||
err = sysext.ExtractFilesFromLastLayer(image, dir, logger, AllowList) | ||
if err != nil { | ||
logger.Logger.Error().Str("image", args.Get(1)).Err(err).Msg("β extracting layer") | ||
} | ||
|
||
// Now create the file that tells systemd that this is a sysext! | ||
err = os.MkdirAll(filepath.Join(dir, "/usr/lib/extension-release.d/"), os.ModeDir|os.ModePerm) | ||
if err != nil { | ||
logger.Logger.Error().Str("dir", filepath.Join(dir, "/usr/lib/extension-release.d/")).Err(err).Msg("β creating dir") | ||
return err | ||
} | ||
|
||
arch := "x86-64" | ||
if ctx.String("arch") == "arm64" { | ||
arch = "arm64" | ||
} | ||
|
||
extensionData := fmt.Sprintf("ID=_any\nARCHITECTURE=%s", arch) | ||
|
||
// If the extension ships any service files, we want this so systemd is reloaded and the service available immediately | ||
if ctx.Bool("service-reload") { | ||
extensionData = fmt.Sprintf("%s\nEXTENSION_RELOAD_MANAGER=1", extensionData) | ||
} | ||
err = os.WriteFile(filepath.Join(dir, "/usr/lib/extension-release.d/", fmt.Sprintf("extension-release.%s", name)), []byte(extensionData), os.ModePerm) | ||
if err != nil { | ||
logger.Logger.Error().Str("file", fmt.Sprintf("extension-release.%s", name)).Err(err).Msg("β creating releasefile") | ||
return err | ||
} | ||
|
||
logger.Logger.Info().Msg("π¦ Packing sysext into raw image") | ||
// Call systemd-repart to create the sysext based off the files | ||
outputFile := fmt.Sprintf("%s.sysext.raw", name) | ||
if outputDir := ctx.String("output"); outputDir != "" { | ||
outputFile = filepath.Join(outputDir, outputFile) | ||
} | ||
// Call systemd-repart to create the sysext based off the files | ||
command := exec.Command( | ||
"systemd-repart", | ||
"--make-ddi=sysext", | ||
"--image-policy=root=verity+signed+absent:usr=verity+signed+absent", | ||
fmt.Sprintf("--architecture=%s", arch), | ||
// Having a fixed predictable seed makes the Image UUID be always the same if the inputs are the same, | ||
// so its a reproducible image. So getting the same files and same cert/key should produce a reproducible image always | ||
// Another layer to verify images, even if its a manual check, we make it easier | ||
fmt.Sprintf("--seed=%s", uuid.NewV5(uuid.NamespaceDNS, "kairos-sysext")), | ||
fmt.Sprintf("--copy-source=%s", dir), | ||
outputFile, // output sysext image | ||
fmt.Sprintf("--private-key=%s", ctx.String("private-key")), | ||
fmt.Sprintf("--certificate=%s", ctx.String("certificate")), | ||
) | ||
out, err := command.CombinedOutput() | ||
logger.Logger.Debug().Str("output", string(out)).Msg("building sysext") | ||
if err != nil { | ||
logger.Logger.Error().Err(err). | ||
Str("command", strings.Join(command.Args, " ")). | ||
Str("output", string(out)). | ||
Msg("β building sysext") | ||
return err | ||
} | ||
|
||
logger.Logger.Info().Str("output", outputFile).Msg("π Done sysext creation") | ||
return nil | ||
}, | ||
} |