Skip to content

Commit bb6b423

Browse files
committed
Add build command for OCI images
This command automates the steps for building OCI images from checkpoint archives. Signed-off-by: Parthiba-Hazra <parthibahazra@gmail.com> Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
1 parent a618f1b commit bb6b423

File tree

4 files changed

+212
-0
lines changed

4 files changed

+212
-0
lines changed

checkpointctl.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ func main() {
3131

3232
rootCommand.AddCommand(cmd.List())
3333

34+
rootCommand.AddCommand(cmd.BuildCmd())
35+
3436
rootCommand.Version = version
3537

3638
if err := rootCommand.Execute(); err != nil {

cmd/build.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package cmd
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"log"
9+
10+
"github.com/checkpoint-restore/checkpointctl/internal"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func BuildCmd() *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "build <checkpoint-path> <image-name>",
17+
Short: "Create an OCI image from a container checkpoint archive",
18+
Long: `The 'build' command converts a container checkpoint archive into an OCI-compatible image.
19+
Metadata from the checkpoint archive is extracted and applied as OCI image annotations.
20+
Example:
21+
checkpointctl build checkpoint.tar quay.io/foo/bar:latest
22+
buildah push quay.io/foo/bar:latest`,
23+
Args: cobra.ExactArgs(2),
24+
RunE: convertArchive,
25+
}
26+
27+
return cmd
28+
}
29+
30+
func convertArchive(cmd *cobra.Command, args []string) error {
31+
if len(args) != 2 {
32+
return fmt.Errorf("please provide both the checkpoint path and the image name")
33+
}
34+
35+
checkpointPath := args[0]
36+
imageName := args[1]
37+
38+
ImageBuilder := internal.NewImageBuilder(imageName, checkpointPath)
39+
40+
err := ImageBuilder.CreateImageFromCheckpoint(context.Background())
41+
if err != nil {
42+
return err
43+
}
44+
45+
log.Printf("Image '%s' created successfully from checkpoint '%s'\n", imageName, checkpointPath)
46+
return nil
47+
}

docs/checkpointctl-build.adoc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
= checkpointctl-build(1)
2+
include::footer.adoc[]
3+
4+
== Name
5+
6+
*checkpointctl-build* - Create OCI image from a checkpoint tar file.
7+
8+
== Synopsis
9+
10+
*checkpointctl build* CHECKPOINT_PATH IMAGE_NAME
11+
12+
== Options
13+
14+
*-h*, *--help*::
15+
Show help for checkpointctl build
16+
17+
== Description
18+
19+
Create an OCI image from a container checkpoint archive (tar file) using `buildah`.
20+
The checkpoint metadata is extracted from the archive and applied as OCI image annotations,
21+
allowing the container runtime (Podman, CRI-O, containerd) to identify the image as a checkpoint.
22+
23+
Usage Example:
24+
`checkpointctl build checkpoint.tar quay.io/foo/bar:latest`
25+
`buildah push quay.io/foo/bar:latest`
26+
27+
== See also
28+
29+
checkpointctl(1)

internal/oci_image_build.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package internal
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"log"
8+
"os"
9+
"os/exec"
10+
"strings"
11+
12+
metadata "github.com/checkpoint-restore/checkpointctl/lib"
13+
)
14+
15+
type ImageBuilder struct {
16+
imageName string
17+
checkpointPath string
18+
}
19+
20+
func NewImageBuilder(imageName, checkpointPath string) *ImageBuilder {
21+
return &ImageBuilder{
22+
imageName: imageName,
23+
checkpointPath: checkpointPath,
24+
}
25+
}
26+
27+
func runBuildahCommand(args ...string) (string, error) {
28+
cmd := exec.Command("buildah", args...)
29+
30+
var out bytes.Buffer
31+
var stderr bytes.Buffer
32+
cmd.Stdout = &out
33+
cmd.Stderr = &stderr
34+
35+
err := cmd.Run()
36+
if err != nil {
37+
return "", fmt.Errorf("buildah command failed: error: %w, stderr: %s", err, stderr.String())
38+
}
39+
40+
return out.String(), nil
41+
}
42+
43+
func (ic *ImageBuilder) CreateImageFromCheckpoint(ctx context.Context) error {
44+
// Step 1: Create a new container from scratch
45+
newContainer, err := runBuildahCommand("from", "scratch")
46+
if err != nil {
47+
return fmt.Errorf("creating container failed: %w", err)
48+
}
49+
newContainer = strings.TrimSpace(newContainer)
50+
51+
// Ensure the container is removed in case of failure
52+
defer func() {
53+
if newContainer != "" {
54+
_, err := runBuildahCommand("rm", newContainer)
55+
if err != nil {
56+
fmt.Printf("Warning: failed to remove container %s: %v\n", newContainer, err)
57+
}
58+
}
59+
}()
60+
61+
// Step 2: Add checkpoint files to the container
62+
_, err = runBuildahCommand("add", newContainer, ic.checkpointPath)
63+
if err != nil {
64+
return fmt.Errorf("adding files to container failed: %w", err)
65+
}
66+
67+
// Step 3: Apply checkpoint annotations
68+
checkpointImageAnnotations, err := ic.getCheckpointAnnotations()
69+
if err != nil {
70+
return fmt.Errorf("extracting checkpoint annotations failed: %w", err)
71+
}
72+
73+
for key, value := range checkpointImageAnnotations {
74+
_, err := runBuildahCommand("config", "--annotation", fmt.Sprintf("%s=%s", key, value), newContainer)
75+
if err != nil {
76+
fmt.Printf("Error setting annotation %s=%s: %v\n", key, value, err)
77+
} else {
78+
fmt.Printf("Added annotation: %s=%s\n", key, value)
79+
}
80+
}
81+
82+
// Step 4: Commit the container to an image
83+
_, err = runBuildahCommand("commit", newContainer, ic.imageName)
84+
if err != nil {
85+
return fmt.Errorf("committing container annotations failed: %w", err)
86+
}
87+
88+
return nil
89+
}
90+
91+
func (ic *ImageBuilder) getCheckpointAnnotations() (map[string]string, error) {
92+
checkpointImageAnnotations := map[string]string{}
93+
94+
// Create a temporary directory
95+
tempDir, err := os.MkdirTemp("", "checkpoint-extract-")
96+
if err != nil {
97+
log.Printf("Error creating temporary directory: %v\n", err)
98+
return nil, err
99+
}
100+
defer os.RemoveAll(tempDir)
101+
102+
filesToExtract := []string{"spec.dump", "config.dump"}
103+
if err = UntarFiles(ic.checkpointPath, tempDir, filesToExtract); err != nil {
104+
log.Printf("Error extracting files from archive %s: %v\n", ic.checkpointPath, err)
105+
return nil, err
106+
}
107+
108+
info := &checkpointInfo{}
109+
info.configDump, _, err = metadata.ReadContainerCheckpointConfigDump(tempDir)
110+
if err != nil {
111+
return nil, err
112+
}
113+
114+
info.specDump, _, err = metadata.ReadContainerCheckpointSpecDump(tempDir)
115+
if err != nil {
116+
return nil, err
117+
}
118+
119+
info.containerInfo, err = getContainerInfo(info.specDump, info.configDump)
120+
if err != nil {
121+
return nil, err
122+
}
123+
124+
checkpointImageAnnotations[metadata.CheckpointAnnotationEngine] = info.containerInfo.Engine
125+
checkpointImageAnnotations[metadata.CheckpointAnnotationName] = info.containerInfo.Name
126+
checkpointImageAnnotations[metadata.CheckpointAnnotationPod] = info.containerInfo.Pod
127+
checkpointImageAnnotations[metadata.CheckpointAnnotationNamespace] = info.containerInfo.Namespace
128+
checkpointImageAnnotations[metadata.CheckpointAnnotationRootfsImageUserRequested] = info.configDump.RootfsImage
129+
checkpointImageAnnotations[metadata.CheckpointAnnotationRootfsImageName] = info.configDump.RootfsImageName
130+
checkpointImageAnnotations[metadata.CheckpointAnnotationRootfsImageID] = info.configDump.RootfsImageRef
131+
checkpointImageAnnotations[metadata.CheckpointAnnotationRuntimeName] = info.configDump.OCIRuntime
132+
133+
return checkpointImageAnnotations, nil
134+
}

0 commit comments

Comments
 (0)