Skip to content

Commit

Permalink
Add support to build OCI images from checkpoint archives
Browse files Browse the repository at this point in the history
- With this enhancement, users can now build OCI images from
  checkpoint archives using the `checkpointctl build` command.

  This command accepts checkpoint archive and a image name as input
  and generates an OCI image suitable for use with container runtimes
  like CRI-O or Podman. Users can inspect the image to get information
  about runtime, container, pod, namespace, image name etc.

Signed-off-by: Parthiba-Hazra <[email protected]>
  • Loading branch information
Parthiba-Hazra committed Apr 5, 2024
1 parent 6c3a263 commit 8496613
Show file tree
Hide file tree
Showing 7 changed files with 314 additions and 1 deletion.
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,17 @@ release:
CGO_ENABLED=0 $(GO_BUILD) -o $(NAME) -ldflags "-X main.name=$(NAME) -X main.version=${VERSION}"

.PHONY: install
install: $(NAME) install.completions
install: $(NAME) install.completions install-scripts
@echo " INSTALL " $<
@mkdir -p $(DESTDIR)$(BINDIR)
@install -m0755 $< $(DESTDIR)$(BINDIR)
@make -C docs install

.PHONY: install-scripts
install-scripts:
@echo " INSTALL SCRIPTS"
@install -m0755 internal/scripts/build_image.sh $(DESTDIR)$(BINDIR)

.PHONY: uninstall
uninstall: uninstall.completions
@make -C docs uninstall
Expand Down
2 changes: 2 additions & 0 deletions checkpointctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func main() {

rootCommand.AddCommand(cmd.List())

rootCommand.AddCommand(cmd.BuildCmd())

rootCommand.Version = version

if err := rootCommand.Execute(); err != nil {
Expand Down
41 changes: 41 additions & 0 deletions cmd/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
"context"
"fmt"
"log"

"github.com/checkpoint-restore/checkpointctl/internal"
"github.com/spf13/cobra"
)

func BuildCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "build [checkpoint-path] [image-name]",
Short: "Create an OCI image from a container checkpoint archive",
RunE: convertArchive,
}

return cmd
}

func convertArchive(cmd *cobra.Command, args []string) error {
if len(args) != 2 {
return fmt.Errorf("please provide both the checkpoint path and the image name")
}

checkpointPath := args[0]
imageName := args[1]

imageCreater := internal.NewImageCreator(imageName, checkpointPath)

err := imageCreater.CreateImageFromCheckpoint(context.Background())
if err != nil {
return err
}

log.Printf("Image '%s' created successfully from checkpoint '%s'\n", imageName, checkpointPath)
return nil
}
25 changes: 25 additions & 0 deletions docs/checkpointctl-build.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
= checkpointctl-build(1)
include::footer.adoc[]

== Name

*checkpointctl-build* - Create OCI image from a checkpoint tar file.

== Synopsis

*checkpointctl build* CHECKPOINT_PATH IMAGE_NAME

== Options

*-h*, *--help*::
Show help for checkpointctl build

== Description

Creates an OCI image from a checkpoint tar file. This command requires `buildah` to be installed on the system.

Please ensure that `buildah` is installed before running this command.

== See also

checkpointctl(1)
83 changes: 83 additions & 0 deletions internal/annotations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package internal

const (
// CheckpointAnnotationContianerMnager is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the name of container manager.
CheckpointAnnotationContianerMnager = "io.container.manager"

// CrioCheckpointAnnotationName is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the name of the checkpoint.
CrioCheckpointAnnotationName = "io.kubernetes.cri-o.annotations.checkpoint.name"

// CrioCheckpointAnnotationPod is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the name of the pod associated with the checkpoint.
CrioCheckpointAnnotationPod = "io.kubernetes.cri-o.annotations.checkpoint.pod"

// CrioCheckpointAnnotationNamespace is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the namespace of the pod associated with the checkpoint.
CrioCheckpointAnnotationNamespace = "io.kubernetes.cri-o.annotations.checkpoint.namespace"

// CrioCheckpointAnnotationRootfsImage is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the root filesystem image associated with the checkpoint.
CrioCheckpointAnnotationRootfsImage = "io.kubernetes.cri-o.annotations.checkpoint.rootfsImage"

// CrioCheckpointAnnotationRootfsImageID is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the ID of the root filesystem image associated with the checkpoint.
CrioCheckpointAnnotationRootfsImageID = "io.kubernetes.cri-o.annotations.checkpoint.rootfsImageID"

// CrioCheckpointAnnotationRootfsImageName is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the name of the root filesystem image associated with the checkpoint.
CrioCheckpointAnnotationRootfsImageName = "io.kubernetes.cri-o.annotations.checkpoint.rootfsImageName"

// CrioCheckpointAnnotationRuntimeName is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the runtime used on the host where the checkpoint was created.
CrioCheckpointAnnotationRuntimeName = "io.kubernetes.cri-o.annotations.checkpoint.runtime.name"

// ContainerdCheckpointAnnotationName is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the name of the checkpoint.
ContainerdCheckpointAnnotationName = "io.kubernetes.cri.annotations.checkpoint.name"

// ContainerdCheckpointAnnotationPod is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the name of the pod associated with the checkpoint.
ContainerdCheckpointAnnotationPod = "io.kubernetes.cri.annotations.checkpoint.pod"

// ContainerdCheckpointAnnotationNamespace is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the namespace of the pod associated with the checkpoint.
ContainerdCheckpointAnnotationNamespace = "io.kubernetes.cri.annotations.checkpoint.namespace"

// ContainerdCheckpointAnnotationRootfsImage is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the root filesystem image associated with the checkpoint.
ContainerdCheckpointAnnotationRootfsImage = "io.kubernetes.cri.annotations.checkpoint.rootfsImage"

// ContainerdCheckpointAnnotationRootfsImageID is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the ID of the root filesystem image associated with the checkpoint.
ContainerdCheckpointAnnotationRootfsImageID = "io.kubernetes.cri.annotations.checkpoint.rootfsImageID"

// ContainerdCheckpointAnnotationRootfsImageName is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the name of the root filesystem image associated with the checkpoint.
ContainerdCheckpointAnnotationRootfsImageName = "io.kubernetes.cri.annotations.checkpoint.rootfsImageName"

// ContainerdCheckpointAnnotationRuntimeName is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the runtime used on the host where the checkpoint was created.
ContainerdCheckpointAnnotationRuntimeName = "io.kubernetes.cri.annotations.checkpoint.runtime.name"

// PodmanCheckpointAnnotationName is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the name of the checkpoint.
PodmanCheckpointAnnotationName = "io.podman.annotations.checkpoint.name"

// PodmanCheckpointAnnotationRootfsImage is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the root filesystem image associated with the checkpoint.
PodmanCheckpointAnnotationRootfsImage = "io.podman.annotations.checkpoint.rootfsImage"

// PodmanCheckpointAnnotationRootfsImageID is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the ID of the root filesystem image associated with the checkpoint.
PodmanCheckpointAnnotationRootfsImageID = "io.podman.annotations.checkpoint.rootfsImageID"

// PodmanCheckpointAnnotationRootfsImageName is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the name of the root filesystem image associated with the checkpoint.
PodmanCheckpointAnnotationRootfsImageName = "io.podman.annotations.checkpoint.rootfsImageName"

// PodmanCheckpointAnnotationRuntimeName is used by checkpointctl when creating an OCI image
// from a checkpoint archive to specify the runtime used on the host where the checkpoint was created.
PodmanCheckpointAnnotationRuntimeName = "io.podman.annotations.checkpoint.runtime.name"
)
129 changes: 129 additions & 0 deletions internal/image_from_checkpoint_archive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package internal

import (
"bytes"
"context"
"fmt"
"log"
"os"
"os/exec"

metadata "github.com/checkpoint-restore/checkpointctl/lib"
)

const BUILD_SCRIPT = "build_image.sh"

type ImageCreator struct {
imageName string
checkpointPath string
}

func NewImageCreator(imageName, checkpointPath string) *ImageCreator {
return &ImageCreator{
imageName: imageName,
checkpointPath: checkpointPath,
}
}

func (ic *ImageCreator) CreateImageFromCheckpoint(ctx context.Context) error {
tempDir, err := os.MkdirTemp("", "checkpoint_tmp")
if err != nil {
return err
}
defer os.RemoveAll(tempDir)

annotationsFilePath, err := ic.setCheckpointAnnotations(tempDir)
if err != nil {
return err
}

var stdout bytes.Buffer
var stderr bytes.Buffer
cmd := exec.Command(BUILD_SCRIPT, annotationsFilePath, ic.checkpointPath, ic.imageName)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err = cmd.Run()
if err != nil {
return fmt.Errorf("failed to execute script: %v, %v, %w", stdout.String(), stderr.String(), err)
}

return nil
}

func writeAnnotationsToFile(tempDir string, annotations map[string]string) (string, error) {
tempFile, err := os.CreateTemp(tempDir, "annotations_*.txt")
if err != nil {
return "", err
}
defer tempFile.Close()

for key, value := range annotations {
_, err := fmt.Fprintf(tempFile, "%s=%s\n", key, value)
if err != nil {
return "", err
}
}

return tempFile.Name(), nil
}

func (ic *ImageCreator) setCheckpointAnnotations(tempDir string) (string, error) {
filesToExtract := []string{"spec.dump", "config.dump"}
if err := UntarFiles(ic.checkpointPath, tempDir, filesToExtract); err != nil {
log.Printf("Error extracting files from archive %s: %v\n", ic.checkpointPath, err)
return "", err
}

var err error
info := &checkpointInfo{}
info.configDump, _, err = metadata.ReadContainerCheckpointConfigDump(tempDir)
if err != nil {
return "", err
}

info.specDump, _, err = metadata.ReadContainerCheckpointSpecDump(tempDir)
if err != nil {
return "", err
}

info.containerInfo, err = getContainerInfo(info.specDump, info.configDump)
if err != nil {
return "", err
}

checkpointImageAnnotations := map[string]string{}
switch info.containerInfo.Engine {
case "CRI-O":
checkpointImageAnnotations[CheckpointAnnotationContianerMnager] = info.containerInfo.Engine
checkpointImageAnnotations[CrioCheckpointAnnotationName] = info.containerInfo.Name
checkpointImageAnnotations[CrioCheckpointAnnotationPod] = info.containerInfo.Pod
checkpointImageAnnotations[CrioCheckpointAnnotationNamespace] = info.containerInfo.Namespace
checkpointImageAnnotations[CrioCheckpointAnnotationRootfsImage] = info.configDump.RootfsImage
checkpointImageAnnotations[CrioCheckpointAnnotationRootfsImageName] = info.configDump.RootfsImageName
checkpointImageAnnotations[CrioCheckpointAnnotationRootfsImageID] = info.configDump.RootfsImageRef
checkpointImageAnnotations[CrioCheckpointAnnotationRuntimeName] = info.configDump.OCIRuntime
case "libpod":
checkpointImageAnnotations[CheckpointAnnotationContianerMnager] = info.containerInfo.Engine
checkpointImageAnnotations[PodmanCheckpointAnnotationName] = info.containerInfo.Name
checkpointImageAnnotations[PodmanCheckpointAnnotationRootfsImage] = info.configDump.RootfsImage
checkpointImageAnnotations[PodmanCheckpointAnnotationRootfsImageName] = info.configDump.RootfsImageName
checkpointImageAnnotations[PodmanCheckpointAnnotationRootfsImageID] = info.configDump.RootfsImageRef
checkpointImageAnnotations[PodmanCheckpointAnnotationRuntimeName] = info.configDump.OCIRuntime
default:
checkpointImageAnnotations[CheckpointAnnotationContianerMnager] = info.containerInfo.Engine
checkpointImageAnnotations[ContainerdCheckpointAnnotationName] = info.containerInfo.Name
checkpointImageAnnotations[ContainerdCheckpointAnnotationPod] = info.containerInfo.Pod
checkpointImageAnnotations[ContainerdCheckpointAnnotationNamespace] = info.containerInfo.Namespace
checkpointImageAnnotations[ContainerdCheckpointAnnotationRootfsImage] = info.configDump.RootfsImage
checkpointImageAnnotations[ContainerdCheckpointAnnotationRootfsImageName] = info.configDump.RootfsImageName
checkpointImageAnnotations[ContainerdCheckpointAnnotationRootfsImageID] = info.configDump.RootfsImageRef
checkpointImageAnnotations[ContainerdCheckpointAnnotationRuntimeName] = info.configDump.OCIRuntime
}

annotationsFilePath, err := writeAnnotationsToFile(tempDir, checkpointImageAnnotations)
if err != nil {
return "", err
}

return annotationsFilePath, nil
}
28 changes: 28 additions & 0 deletions internal/scripts/build_image.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/bash

set -euo pipefail

if ! command -v buildah &> /dev/null; then
echo "buildah is not installed. Please install buildah before running 'checkpointctl build' command."
exit 1
fi

annotationsFilePath="$1"
checkpointPath="$2"
imageName="$3"

newcontainer=$(buildah from scratch)

buildah add "$newcontainer" "$checkpointPath"

while IFS= read -r line; do
key=$(echo "$line" | cut -d '=' -f 1)
value=$(echo "$line" | cut -d '=' -f 2-)
buildah config --annotation "$key=$value" "$newcontainer"
done < "$annotationsFilePath"

buildah commit "$newcontainer" "$imageName"

buildah rm "$newcontainer"

echo "Checkpoint image created successfully: $imageName"

0 comments on commit 8496613

Please sign in to comment.