Skip to content
This repository has been archived by the owner on Oct 27, 2023. It is now read-only.

Commit

Permalink
Merge pull request #7 from leo8a/oneimage
Browse files Browse the repository at this point in the history
Initial implementation to create a single OCI seed image
  • Loading branch information
leo8a authored Oct 17, 2023
2 parents d9c3b80 + 52ab39c commit 4d93dab
Show file tree
Hide file tree
Showing 23 changed files with 11,749 additions and 130 deletions.
203 changes: 110 additions & 93 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ var createCmd = &cobra.Command{
},
}

// rpmOstreeClient creates a new rpm ostree client for the IBU imager
var rpmOstreeClient = NewClient("ibu-imager")

func init() {

// Add create command
Expand Down Expand Up @@ -73,9 +76,9 @@ func create() {
systemdObj := conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1")

//
// Save list of running containers
// Save list of running containers and current clusterversion
//
log.Println("Saving list of running containers.")
log.Println("Saving list of running containers, catalogsources, and clusterversion.")

// Check if the file /var/tmp/container_list.done does not exist
if _, err = os.Stat("/var/tmp/container_list.done"); os.IsNotExist(err) {
Expand All @@ -85,19 +88,31 @@ func create() {
err = os.MkdirAll(backupDir, os.ModePerm)
check(err)

// Execute 'crictl ps -o json' command, parse the JSON output and extract image references using 'jq'
// Execute 'crictl images -o json' command, parse the JSON output and extract image references using 'jq'
log.Debug("Save list of running containers")
criListContainers := fmt.Sprintf(`nsenter --target 1 --cgroup --mount --ipc --pid -- crictl images -o json | jq -r '.images[] | .repoDigests[], .repoTags[]' > ` + backupDir + `/containers.list`)
err = runCMD(criListContainers)
_, err = runInHostNamespace(
"crictl", append([]string{"images", "-o", "json", "|", "jq", "-r", "'.images[] | .repoDigests[], .repoTags[]'"}, ">", backupDir+"/containers.list")...)
check(err)

// Execute 'oc get catalogsource' command, parse the JSON output and extract image references using 'jq'
log.Debug("Save catalog source images")
_, err = runInHostNamespace(
"oc", append([]string{"get", "catalogsource", "-A", "-o", "json", "--kubeconfig", kubeconfigFile, "|", "jq", "-r", "'.items[].spec.image'"}, ">", backupDir+"/catalogimages.list")...)
check(err)

// Execute 'oc get clusterversion' command and save it
log.Debug("Save clusterversion to file")
_, err = runInHostNamespace(
"oc", append([]string{"get", "clusterversion", "version", "-o", "json", "--kubeconfig", kubeconfigFile}, ">", backupDir+"/clusterversion.json")...)
check(err)

// Create the file /var/tmp/container_list.done
err = runCMD("touch /var/tmp/container_list.done")
_, err = os.Create("/var/tmp/container_list.done")
check(err)

log.Println("List of containers saved successfully.")
log.Println("List of containers, catalogsources, and clusterversion saved successfully.")
} else {
log.Println("Skipping list of containers already exists.")
log.Println("Skipping list of containers, catalogsources, and clusterversion already exists.")
}

//
Expand All @@ -115,25 +130,26 @@ func create() {
log.Println("Stopping containers and CRI-O runtime.")

// Store current status of CRI-O systemd
crioService := fmt.Sprintf(`nsenter --target 1 --cgroup --mount --ipc --pid -- systemctl is-active crio > %s/crio.systemd.status`, backupDir)
_ = runCMD(crioService) // this commands returns 3 when crio is inactive
_, err = runInHostNamespace(
"systemctl", append([]string{"is-active", "crio"}, ">", backupDir+"/crio.systemd.status")...)
check(err)

// Read CRI-O systemd status from file
crioSystemdStatus, _ := readLineFromFile(backupDir + "/crio.systemd.status")

if crioSystemdStatus == "active" {

// CRI-O is active, so stop running containers
criStopContainers := fmt.Sprintf(`crictl ps -q | xargs --no-run-if-empty --max-args 1 --max-procs 10 crictl stop --timeout 5`)
log.Debug("Stop running containers")
err = runCMD(criStopContainers)
_, err = runInHostNamespace(
"crictl", []string{"ps", "-q", "|", "xargs", "--no-run-if-empty", "--max-args", "1", "--max-procs", "10", "crictl", "stop", "--timeout", "5"}...)
check(err)

// Waiting for containers to stop
waitCMD := fmt.Sprintf(`while crictl ps -q | grep -q . ; do sleep 1 ; done`)
log.Debug("Wait for containers to stop")
err = runCMD(waitCMD)
check(err)
// Waiting for containers to stop (TODO: implement this using runInHostNamespace)
//waitCMD := fmt.Sprintf(`while crictl ps -q | grep -q . ; do sleep 1 ; done`)
//log.Debug("Wait for containers to stop")
//err = runCMD(waitCMD)
//check(err)

// Execute a D-Bus call to stop the CRI-O runtime
log.Debug("Stopping CRI-O engine")
Expand All @@ -151,7 +167,8 @@ func create() {
log.Println("Create backup datadir")

// Check if the backup file for /var doesn't exist
if _, err := os.Stat(backupDir + "/var.tgz"); os.IsNotExist(err) {
varTarFile := backupDir + "/var.tgz"
if _, err = os.Stat(varTarFile); os.IsNotExist(err) {

// Define the 'exclude' patterns
excludePatterns := []string{
Expand All @@ -164,15 +181,15 @@ func create() {
}

// Build the tar command
args := []string{"czf", fmt.Sprintf("%s/var.tgz", backupDir)}
tarArgs := []string{"czf", varTarFile}
for _, pattern := range excludePatterns {
// We're handling the excluded patterns in bash, we need to single quote them to prevent expansion
args = append(args, "--exclude", fmt.Sprintf("'%s'", pattern))
tarArgs = append(tarArgs, "--exclude", fmt.Sprintf("'%s'", pattern))
}
args = append(args, "--selinux", sourceDir)
tarArgs = append(tarArgs, "--selinux", sourceDir)

// Run the tar command
err = runCMD("tar " + strings.Join(args, " "))
_, err = runInHostNamespace("tar", strings.Join(tarArgs, " "))
check(err)

log.Println("Backup of /var created successfully.")
Expand All @@ -181,118 +198,118 @@ func create() {
}

// Check if the backup file for /etc doesn't exist
if _, err := os.Stat(backupDir + "/etc.tgz"); os.IsNotExist(err) {
if _, err = os.Stat(backupDir + "/etc.tgz"); os.IsNotExist(err) {

// Execute 'ostree admin config-diff' command and backup /etc
ostreeAdminCMD := fmt.Sprintf(`nsenter --target 1 --cgroup --mount --ipc --pid -- ostree admin config-diff | awk '{print "/etc/" $2}' | xargs tar czf %s/etc.tgz --selinux`, backupDir)
err = runCMD(ostreeAdminCMD)
_, err = runInHostNamespace(
"ostree", []string{"admin", "config-diff", "|", "awk", `'{print "/etc/" $2}'`, "|", "xargs", "tar", "czf", backupDir + "/etc.tgz", "--selinux"}...)
check(err)

log.Println("Backup of /etc created successfully.")
} else {
log.Println("Skipping etc backup as it already exists.")
}

// Check if the backup file for rpm-ostree doesn't exist
if _, err := os.Stat(backupDir + "/rpm-ostree.json"); os.IsNotExist(err) {
// Check if the backup file for ostree doesn't exist
if _, err = os.Stat(backupDir + "/ostree.tgz"); os.IsNotExist(err) {

// Execute 'rpm-ostree status' command and backup its output
rpmOStreeCMD := fmt.Sprintf(`nsenter --target 1 --cgroup --mount --ipc --pid -- rpm-ostree status -v --json > %s/rpm-ostree.json`, backupDir)
err = runCMD(rpmOStreeCMD)
// Execute 'tar' command and backup /etc
_, err = runInHostNamespace(
"tar", []string{"czf", backupDir + "/ostree.tgz", "--selinux", "-C", "/ostree/repo", "."}...)
check(err)

log.Println("Backup of rpm-ostree created successfully.")
log.Println("Backup of ostree created successfully.")
} else {
log.Println("Skipping rpm-ostree backup as it already exists.")
log.Println("Skipping ostree backup as it already exists.")
}

// Check if the backup file for mco-currentconfig doesn't exist
if _, err = os.Stat(backupDir + "/mco-currentconfig.json"); os.IsNotExist(err) {
// Check if the backup file for rpm-ostree doesn't exist
if _, err = os.Stat(backupDir + "/rpm-ostree.json"); os.IsNotExist(err) {

// Execute 'copy' command and backup mco-currentconfig
backupCurrentConfigCMD := fmt.Sprintf(`cp /etc/machine-config-daemon/currentconfig %s/mco-currentconfig.json`, backupDir)
err = runCMD(backupCurrentConfigCMD)
// Execute 'rpm-ostree status' command and backup mco-currentconfig
_, err = runInHostNamespace(
"rpm-ostree", append([]string{"status", "-v", "--json"}, ">", backupDir+"/rpm-ostree.json")...)
check(err)

log.Println("Backup of mco-currentconfig created successfully.")
log.Println("Backup of rpm-ostree.json created successfully.")
} else {
log.Println("Skipping mco-currentconfig backup as it already exists.")
log.Println("Skipping rpm-ostree.json backup as it already exists.")
}

// Check if the commit backup doesn't exist
if _, err = os.Stat(backupDir + "/ostree.commit"); os.IsNotExist(err) {
// Check if the backup file for mco-currentconfig doesn't exist
if _, err = os.Stat(backupDir + "/mco-currentconfig.json"); os.IsNotExist(err) {

// Execute 'ostree commit' command
ostreeCommitCMD := fmt.Sprintf(`nsenter --target 1 --cgroup --mount --ipc --pid -- ostree commit --branch %s %s > %s/ostree.commit`, backupTag, backupDir, backupDir)
err = runCMD(ostreeCommitCMD)
// Execute 'copy' command and backup mco-currentconfig
_, err = runInHostNamespace(
"cp", "/etc/machine-config-daemon/currentconfig", backupDir+"/mco-currentconfig.json")
check(err)

log.Debug("Commit backup created successfully.")
log.Println("Backup of mco-currentconfig created successfully.")
} else {
log.Debug("Skipping backup commit as it already exists.")
log.Println("Skipping mco-currentconfig backup as it already exists.")
}

//
// Encapsulating and pushing backup OCI image
//
log.Printf("Encapsulate and push backup OCI image to %s:%s.", containerRegistry, backupTag)

// Execute 'ostree container encapsulate' command for backup OCI image
ostreeEncapsulateBackupCMD := fmt.Sprintf(`nsenter --target 1 --cgroup --mount --ipc --pid sh -c 'REGISTRY_AUTH_FILE=%s ostree container encapsulate %s registry:%s:%s --repo /ostree/repo --label ostree.bootable=true'`, authFile, backupTag, containerRegistry, backupTag)
err = runCMD(ostreeEncapsulateBackupCMD)
check(err)

//
// Encapsulating and pushing base OCI image
// Building and pushing OCI image
//
log.Printf("Encapsulate and push base OCI image to %s:%s.", containerRegistry, baseTag)

// Create base commit checksum file
ostreeBaseChecksumCMD := fmt.Sprintf(`nsenter --target 1 --cgroup --mount --ipc --pid -- rpm-ostree status -v --json | jq -r '.deployments[] | select(.booted == true).checksum' > /var/tmp/ostree.base.commit`)
err = runCMD(ostreeBaseChecksumCMD)
check(err)

// Read base commit from file
baseCommit, err := readLineFromFile("/var/tmp/ostree.base.commit")
log.Printf("Build and push OCI image to %s:%s.", containerRegistry, backupTag)
log.Debug(rpmOstreeClient.RpmOstreeVersion()) // If verbose, also dump out current rpm-ostree version available

// Execute 'ostree container encapsulate' command for base OCI image
ostreeEncapsulateBaseCMD := fmt.Sprintf(`nsenter --target 1 --cgroup --mount --ipc --pid sh -c 'REGISTRY_AUTH_FILE=%s ostree container encapsulate %s registry:%s:%s --repo /ostree/repo --label ostree.bootable=true'`, authFile, baseCommit, containerRegistry, baseTag)
err = runCMD(ostreeEncapsulateBaseCMD)
// Get the current status of rpm-ostree daemon in the host
statusRpmOstree, err := rpmOstreeClient.QueryStatus()
check(err)

//
// Encapsulating and pushing parent OCI image
//
// Get OSName for booted ostree deployment
bootedOSName := statusRpmOstree.Deployments[0].OSName

// Create parent checksum file
ostreeHasParentChecksumCMD := fmt.Sprintf(`nsenter --target 1 --cgroup --mount --ipc --pid -- rpm-ostree status -v --json | jq -r '.deployments[] | select(.booted == true) | has("base-checksum")' > /var/tmp/ostree.has.parent`)
err = runCMD(ostreeHasParentChecksumCMD)
check(err)
// Get ID for booted ostree deployment
bootedID := statusRpmOstree.Deployments[0].ID

// Read hasParent commit from file
hasParent, err := readLineFromFile("/var/tmp/ostree.has.parent")
// Get SHA for booted ostree deployment
bootedDeployment := strings.Split(bootedID, "-")[1]

// Check if current ostree deployment has a parent commit
if hasParent == "true" {
log.Info("OCI image has a parent commit to be encapsulated.")
// Check if the backup file for .origin doesn't exist
originFileName := fmt.Sprintf("%s/ostree-%s.origin", backupDir, bootedDeployment)
if _, err = os.Stat(originFileName); os.IsNotExist(err) {

// Create parent commit checksum file
ostreeParentChecksumCMD := fmt.Sprintf(`nsenter --target 1 --cgroup --mount --ipc --pid -- rpm-ostree status -v --json | jq -r '.deployments[] | select(.booted == true)."base-checksum"' > /var/tmp/ostree.parent.commit`)
err = runCMD(ostreeParentChecksumCMD)
// Execute 'copy' command and backup .origin file
_, err = runInHostNamespace(
"cp", []string{"/ostree/deploy/" + bootedOSName + "/deploy/" + bootedDeployment + ".origin", originFileName}...)
check(err)

// Read parent commit from file
parentCommit, err := readLineFromFile("/var/tmp/ostree.parent.commit")
log.Println("Backup of .origin created successfully.")
} else {
log.Println("Skipping .origin backup as it already exists.")
}

// Execute 'ostree container encapsulate' command for parent OCI image
log.Printf("Encapsulate and push parent OCI image to %s:%s.", containerRegistry, parentTag)
ostreeEncapsulateParentCMD := fmt.Sprintf(`nsenter --target 1 --cgroup --mount --ipc --pid sh -c 'REGISTRY_AUTH_FILE=%s ostree container encapsulate %s registry:%s:%s --repo /ostree/repo --label ostree.bootable=true'`, authFile, parentCommit, containerRegistry, parentTag)
err = runCMD(ostreeEncapsulateParentCMD)
check(err)
// Create a temporary file for the Dockerfile content
tmpfile, err := os.CreateTemp("/var/tmp", "dockerfile-")
if err != nil {
log.Errorf("Error creating temporary file: %s", err)
}
defer os.Remove(tmpfile.Name()) // Clean up the temporary file

} else {
log.Info("Skipping encapsulate parent commit as it is not present.")
// Write the content to the temporary file
_, err = tmpfile.WriteString(containerFileContent)
if err != nil {
log.Errorf("Error writing to temporary file: %s", err)
}
tmpfile.Close() // Close the temporary file

// Build the single OCI image (note: We could include --squash-all option, as well)
_, err = runInHostNamespace(
"podman", []string{"build",
"-f", tmpfile.Name(),
"-t", containerRegistry + ":" + backupTag,
backupDir}...)
check(err)

// Push the created OCI image to user's repository
_, err = runInHostNamespace(
"podman", []string{"push",
"--authfile", authFile,
containerRegistry + ":" + backupTag}...)
check(err)

log.Printf("OCI image created successfully!")
}
Loading

0 comments on commit 4d93dab

Please sign in to comment.