toolbox/src/pkg/podman/podman.go

380 lines
9.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright © 2019 2022 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package podman
import (
"bytes"
"encoding/json"
"fmt"
"io"
"github.com/HarryMichal/go-version"
"github.com/containers/toolbox/pkg/shell"
"github.com/containers/toolbox/pkg/utils"
"github.com/sirupsen/logrus"
)
type Image struct {
ID string
Names []string
Created string
Labels map[string]string
}
var (
podmanVersion string
)
var (
LogLevel = logrus.ErrorLevel
)
func (image *Image) UnmarshalJSON(data []byte) error {
var raw struct {
ID string
Names []string
Created interface{}
Labels map[string]string
}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
image.ID = raw.ID
image.Names = raw.Names
// Until Podman 2.0.x the field 'Created' held a human-readable string in
// format "5 minutes ago". Since Podman 2.1 the field holds an integer with
// Unix time. Go interprets numbers in JSON as float64.
switch value := raw.Created.(type) {
case string:
image.Created = value
case float64:
image.Created = utils.HumanDuration(int64(value))
}
image.Labels = raw.Labels
return nil
}
// CheckVersion compares provided version with the version of Podman.
//
// Takes in one string parameter that should be in the format that is used for versioning (eg. 1.0.0, 2.5.1-dev).
//
// Returns true if the current version is equal to or higher than the required version.
func CheckVersion(requiredVersion string) bool {
currentVersion, _ := GetVersion()
currentVersion = version.Normalize(currentVersion)
requiredVersion = version.Normalize(requiredVersion)
return version.CompareSimple(currentVersion, requiredVersion) >= 0
}
// ContainerExists checks using Podman if a container with given ID/name exists.
//
// Parameter container is a name or an id of a container.
func ContainerExists(container string) (bool, error) {
logLevelString := LogLevel.String()
args := []string{"--log-level", logLevelString, "container", "exists", container}
exitCode, err := shell.RunWithExitCode("podman", nil, nil, nil, args...)
if exitCode != 0 && err == nil {
err = fmt.Errorf("failed to find container %s", container)
}
if err != nil {
return false, err
}
return true, nil
}
// GetContainers is a wrapper function around `podman ps --format json` command.
//
// Parameter args accepts an array of strings to be passed to the wrapped command (eg. ["-a", "--filter", "123"]).
//
// Returned value is a slice of dynamically unmarshalled json, so it needs to be treated properly.
//
// If a problem happens during execution, first argument is nil and second argument holds the error message.
func GetContainers(args ...string) ([]map[string]interface{}, error) {
var stdout bytes.Buffer
logLevelString := LogLevel.String()
args = append([]string{"--log-level", logLevelString, "ps", "--format", "json"}, args...)
if err := shell.Run("podman", nil, &stdout, nil, args...); err != nil {
return nil, err
}
output := stdout.Bytes()
var containers []map[string]interface{}
if err := json.Unmarshal(output, &containers); err != nil {
return nil, err
}
return containers, nil
}
// GetImages is a wrapper function around `podman images --format json` command.
//
// Parameter args accepts an array of strings to be passed to the wrapped command (eg. ["-a", "--filter", "123"]).
//
// Returned value is a slice of Images.
//
// If a problem happens during execution, first argument is nil and second argument holds the error message.
func GetImages(args ...string) ([]Image, error) {
var stdout bytes.Buffer
logLevelString := LogLevel.String()
args = append([]string{"--log-level", logLevelString, "images", "--format", "json"}, args...)
if err := shell.Run("podman", nil, &stdout, nil, args...); err != nil {
return nil, err
}
data := stdout.Bytes()
var images []Image
if err := json.Unmarshal(data, &images); err != nil {
return nil, err
}
return images, nil
}
// GetVersion returns version of Podman in a string
func GetVersion() (string, error) {
if podmanVersion != "" {
return podmanVersion, nil
}
var stdout bytes.Buffer
logLevelString := LogLevel.String()
args := []string{"--log-level", logLevelString, "version", "--format", "json"}
if err := shell.Run("podman", nil, &stdout, nil, args...); err != nil {
return "", err
}
output := stdout.Bytes()
var jsonoutput map[string]interface{}
if err := json.Unmarshal(output, &jsonoutput); err != nil {
return "", err
}
podmanClientInfoInterface := jsonoutput["Client"]
switch podmanClientInfo := podmanClientInfoInterface.(type) {
case nil:
podmanVersion = jsonoutput["Version"].(string)
case map[string]interface{}:
podmanVersion = podmanClientInfo["Version"].(string)
}
return podmanVersion, nil
}
// ImageExists checks using Podman if an image with given ID/name exists.
//
// Parameter image is a name or an id of an image.
func ImageExists(image string) (bool, error) {
logLevelString := LogLevel.String()
args := []string{"--log-level", logLevelString, "image", "exists", image}
exitCode, err := shell.RunWithExitCode("podman", nil, nil, nil, args...)
if exitCode != 0 && err == nil {
err = fmt.Errorf("failed to find image %s", image)
}
if err != nil {
return false, err
}
return true, nil
}
// Inspect is a wrapper around 'podman inspect' command
//
// Parameter 'typearg' takes in values 'container' or 'image' that is passed to the --type flag
func Inspect(typearg string, target string) (map[string]interface{}, error) {
var stdout bytes.Buffer
logLevelString := LogLevel.String()
args := []string{"--log-level", logLevelString, "inspect", "--format", "json", "--type", typearg, target}
if err := shell.Run("podman", nil, &stdout, nil, args...); err != nil {
return nil, err
}
output := stdout.Bytes()
var info []map[string]interface{}
if err := json.Unmarshal(output, &info); err != nil {
return nil, err
}
return info[0], nil
}
func IsToolboxContainer(container string) (bool, error) {
info, err := Inspect("container", container)
if err != nil {
return false, fmt.Errorf("failed to inspect container %s", container)
}
labels, _ := info["Config"].(map[string]interface{})["Labels"].(map[string]interface{})
if labels["com.github.containers.toolbox"] != "true" && labels["com.github.debarshiray.toolbox"] != "true" {
return false, fmt.Errorf("%s is not a toolbox container", container)
}
return true, nil
}
func IsToolboxImage(image string) (bool, error) {
info, err := Inspect("image", image)
if err != nil {
return false, fmt.Errorf("failed to inspect image %s", image)
}
if info["Labels"] == nil {
return false, fmt.Errorf("%s is not a toolbox image", image)
}
labels := info["Labels"].(map[string]interface{})
if labels["com.github.containers.toolbox"] != "true" && labels["com.github.debarshiray.toolbox"] != "true" {
return false, fmt.Errorf("%s is not a toolbox image", image)
}
return true, nil
}
// Pull pulls an image
//
// authfile is a path to a JSON authentication file and is internally used only
// if it is not an empty string.
func Pull(imageName string, authfile string) error {
logLevelString := LogLevel.String()
args := []string{"--log-level", logLevelString, "pull"}
if authfile != "" {
args = append(args, []string{"--authfile", authfile}...)
}
args = append(args, imageName)
if err := shell.Run("podman", nil, nil, nil, args...); err != nil {
return err
}
return nil
}
func RemoveContainer(container string, forceDelete bool) error {
logrus.Debugf("Removing container %s", container)
logLevelString := LogLevel.String()
args := []string{"--log-level", logLevelString, "rm"}
if forceDelete {
args = append(args, "--force")
}
args = append(args, container)
exitCode, err := shell.RunWithExitCode("podman", nil, nil, nil, args...)
switch exitCode {
case 0:
if err != nil {
panic("unexpected error: 'podman rm' finished successfully")
}
case 1:
err = fmt.Errorf("container %s does not exist", container)
case 2:
err = fmt.Errorf("container %s is running", container)
default:
err = fmt.Errorf("failed to remove container %s", container)
}
if err != nil {
return err
}
return nil
}
func RemoveImage(image string, forceDelete bool) error {
logrus.Debugf("Removing image %s", image)
logLevelString := LogLevel.String()
args := []string{"--log-level", logLevelString, "rmi"}
if forceDelete {
args = append(args, "--force")
}
args = append(args, image)
exitCode, err := shell.RunWithExitCode("podman", nil, nil, nil, args...)
switch exitCode {
case 0:
if err != nil {
panic("unexpected error: 'podman rmi' finished successfully")
}
case 1:
err = fmt.Errorf("image %s does not exist", image)
case 2:
err = fmt.Errorf("image %s has dependent children", image)
default:
err = fmt.Errorf("failed to remove image %s", image)
}
if err != nil {
return err
}
return nil
}
func SetLogLevel(logLevel logrus.Level) {
LogLevel = logLevel
}
func Start(container string, stderr io.Writer) error {
logLevelString := LogLevel.String()
args := []string{"--log-level", logLevelString, "start", container}
if err := shell.Run("podman", nil, nil, stderr, args...); err != nil {
return err
}
return nil
}
func SystemMigrate(ociRuntimeRequired string) error {
logLevelString := LogLevel.String()
args := []string{"--log-level", logLevelString, "system", "migrate"}
if ociRuntimeRequired != "" {
args = append(args, []string{"--new-runtime", ociRuntimeRequired}...)
}
if err := shell.Run("podman", nil, nil, nil, args...); err != nil {
return err
}
return nil
}