Files
colima/cmd/nerdctl.go
T
2026-02-06 11:55:44 +01:00

184 lines
5.5 KiB
Go

package cmd
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/abiosoft/colima/cli"
"github.com/abiosoft/colima/cmd/root"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/environment/container/containerd"
"github.com/abiosoft/colima/util"
"github.com/abiosoft/colima/util/fsutil"
"github.com/abiosoft/colima/util/osutil"
"github.com/spf13/cobra"
)
var nerdctlCmdArgs struct {
force bool
path string
usrBinWriteable bool
isColimaScript bool
}
// nerdctlCmd represents the nerdctl command
var nerdctlCmd = &cobra.Command{
Use: "nerdctl",
Aliases: []string{"nerd", "n"},
Short: "run nerdctl (requires containerd runtime)",
Long: `Run nerdctl to interact with containerd.
This requires containerd runtime.
It is recommended to specify '--' to differentiate from Colima flags.
`,
RunE: func(cmd *cobra.Command, args []string) error {
app := newApp()
r, err := app.Runtime()
if err != nil {
return err
}
if r != containerd.Name {
return fmt.Errorf("nerdctl only supports %s runtime", containerd.Name)
}
// collect CONTAINERD_* and NERDCTL_* environment variables from the host
var envVars []string
for _, env := range os.Environ() {
if strings.HasPrefix(env, "CONTAINERD_") || strings.HasPrefix(env, "NERDCTL_") {
envVars = append(envVars, env)
}
}
var nerdctlArgs []string
if len(envVars) > 0 {
// use 'sudo env VAR=value ... nerdctl' to pass environment variables
nerdctlArgs = append([]string{"sudo", "env"}, envVars...)
nerdctlArgs = append(nerdctlArgs, "nerdctl")
} else {
nerdctlArgs = []string{"sudo", "nerdctl"}
}
nerdctlArgs = append(nerdctlArgs, args...)
return app.SSH(nerdctlArgs...)
},
}
// nerdctlLinkFunc represents the nerdctl command
var nerdctlLinkFunc = func() *cobra.Command {
return &cobra.Command{
Use: "install",
Short: "install nerdctl alias script on the host",
Long: `Install nerdctl alias script on the host. The script will be installed at ` + nerdctlDefaultInstallPath + `.`,
Args: cobra.NoArgs,
PreRun: func(cmd *cobra.Command, args []string) {
// check if /usr/local/bin is writeable and no need for sudo
// if the path is user-specified, ignore.
if nerdctlCmdArgs.path != nerdctlDefaultInstallPath {
return
}
// attempt writing to the /usr/local/bin
tmpFile := filepath.Join(filepath.Dir(nerdctlDefaultInstallPath), "colima.tmp")
if err := os.WriteFile(tmpFile, []byte("tmp"), 0777); err == nil {
nerdctlCmdArgs.usrBinWriteable = true
_ = os.Remove(tmpFile)
}
// check if the current file (if exists) is generated by colima
// in such case no need for confirmation before overwrite
// TODO: this is too basic, should be better
if b, err := os.ReadFile(nerdctlCmdArgs.path); err == nil {
if strings.Contains(string(b), "colima nerdctl ") {
nerdctlCmdArgs.isColimaScript = true
}
}
},
RunE: func(cmd *cobra.Command, args []string) error {
exists := false
if _, err := os.Stat(nerdctlCmdArgs.path); err == nil && !nerdctlCmdArgs.force && !nerdctlCmdArgs.isColimaScript {
return fmt.Errorf("%s exists, use --force to replace", nerdctlCmdArgs.path)
} else if err == nil {
exists = true
}
var values = struct {
ColimaApp string
Profile string
}{
ColimaApp: osutil.Executable(),
Profile: config.CurrentProfile().ShortName,
}
buf, err := util.ParseTemplate(nerdctlScript, values)
if err != nil {
return fmt.Errorf("error applying nerdctl script template: %w", err)
}
// /usr/local/bin writeable i.e. sudo not needed
// or user-specified install path, we assume user specified path is writeable
if nerdctlCmdArgs.usrBinWriteable || nerdctlCmdArgs.path != nerdctlDefaultInstallPath {
if exists {
if err := os.Rename(nerdctlCmdArgs.path, nerdctlCmdArgs.path+".moved"); err != nil {
return fmt.Errorf("error backing up existing file: %w", err)
}
}
if err := fsutil.MkdirAll("/usr/local/bin", 0755); err != nil {
return nil
}
return os.WriteFile(nerdctlCmdArgs.path, buf, 0755)
}
// sudo is needed for the default path
log.Println("/usr/local/bin not writable, sudo password required to install nerdctl binary")
if exists && !nerdctlCmdArgs.isColimaScript {
c := cli.CommandInteractive("sudo", "mv", nerdctlCmdArgs.path, nerdctlCmdArgs.path+".moved")
if err := c.Run(); err != nil {
return fmt.Errorf("error backing up existing file: %w", err)
}
}
// prepare dir
{
c := cli.CommandInteractive("sudo", "mkdir", "-p", "/usr/local/bin")
if err := c.Run(); err != nil {
return err
}
}
// install script
{
c := cli.CommandInteractive("sudo", "sh", "-c", "cat > "+nerdctlCmdArgs.path)
c.Stdin = bytes.NewReader(buf)
if err := c.Run(); err != nil {
return err
}
}
// ensure it is executable
if err := cli.Command("sudo", "chmod", "+x", nerdctlCmdArgs.path).Run(); err != nil {
return err
}
return nil
},
}
}
const nerdctlDefaultInstallPath = "/usr/local/bin/nerdctl"
const nerdctlScript = `#!/usr/bin/env sh
{{.ColimaApp}} nerdctl --profile {{.Profile}} -- "$@"
`
func init() {
root.Cmd().AddCommand(nerdctlCmd)
nerdctlLink := nerdctlLinkFunc()
nerdctlCmd.AddCommand(nerdctlLink)
nerdctlLink.Flags().BoolVarP(&nerdctlCmdArgs.force, "force", "f", false, "replace "+nerdctlDefaultInstallPath+" (if exists)")
nerdctlLink.Flags().StringVar(&nerdctlCmdArgs.path, "path", nerdctlDefaultInstallPath, "path to install nerdctl binary")
}