mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
* Add test highlighting the issue and setting the expectations * Add a timeout of 1s to the 'podman version' command This command is called at dependency injection time to initialize a (nil-able) Podman client, even if users won't use Podman at all. As discussed, this command is supposed to be quite fast to return, hence this timeout of 1 second. Initially, we were using cmd.Output to get the command output, but as reported in [1], cmd.Output does not respect the context timeout. This explains the workaround of reading from both stdout and stderr pipes, *and* relying on cmd.Wait() to close those pipes properly when the program exits (either as expected or when the timeout is reached). [1] https://github.com/golang/go/issues/57129 Co-authored-by: Philippe Martin <phmartin@redhat.com> * Log the errors returned at dependency injection time when the optional Kubernetes/Podman clients could not be initialized This helps debug such potential issues instead of swallowing the errors. * Make the timeout configurable via the 'PODMAN_CMD_INIT_TIMEOUT' env var This will allow setting a different value for environments like in GitHub where the Podman client would take slightly more time to return (I guess because of we are running a lot of Podman commands in parallel?). * Increase the timeout for Podman tests to an arbitrary value of 10s Some tests did not pass because the Podman client did not initialize in 1s; I guess because we are running a lot of Podman commands in parallel? This should hopefully improve this situation. * fixup! Add a timeout of 1s to the 'podman version' command --------- Co-authored-by: Philippe Martin <phmartin@redhat.com>
248 lines
6.4 KiB
Go
248 lines
6.4 KiB
Go
package podman
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
|
|
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
|
"github.com/redhat-developer/odo/pkg/platform"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json"
|
|
"k8s.io/klog"
|
|
"k8s.io/kubectl/pkg/scheme"
|
|
)
|
|
|
|
type PodmanCli struct {
|
|
podmanCmd string
|
|
podmanCmdInitTimeout time.Duration
|
|
containerRunGlobalExtraArgs []string
|
|
containerRunExtraArgs []string
|
|
}
|
|
|
|
var _ Client = (*PodmanCli)(nil)
|
|
var _ platform.Client = (*PodmanCli)(nil)
|
|
|
|
// NewPodmanCli returns a new podman client, or nil if the podman command is not accessible in the system
|
|
func NewPodmanCli(ctx context.Context) (*PodmanCli, error) {
|
|
// Check if podman is available in the system
|
|
cli := &PodmanCli{
|
|
podmanCmd: envcontext.GetEnvConfig(ctx).PodmanCmd,
|
|
podmanCmdInitTimeout: envcontext.GetEnvConfig(ctx).PodmanCmdInitTimeout,
|
|
containerRunGlobalExtraArgs: envcontext.GetEnvConfig(ctx).OdoContainerBackendGlobalArgs,
|
|
containerRunExtraArgs: envcontext.GetEnvConfig(ctx).OdoContainerRunArgs,
|
|
}
|
|
version, err := cli.Version(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if version.Client == nil {
|
|
return nil, fmt.Errorf("executable %q not recognized as podman client", cli.podmanCmd)
|
|
}
|
|
|
|
return cli, nil
|
|
}
|
|
|
|
func (o *PodmanCli) PlayKube(pod *corev1.Pod) error {
|
|
serializer := jsonserializer.NewSerializerWithOptions(
|
|
jsonserializer.SimpleMetaFactory{},
|
|
scheme.Scheme,
|
|
scheme.Scheme,
|
|
jsonserializer.SerializerOptions{
|
|
Yaml: true,
|
|
},
|
|
)
|
|
|
|
// +3 because of "play kube -"
|
|
args := make([]string, 0, len(o.containerRunGlobalExtraArgs)+len(o.containerRunExtraArgs)+3)
|
|
args = append(args, o.containerRunGlobalExtraArgs...)
|
|
args = append(args, "play", "kube")
|
|
args = append(args, o.containerRunExtraArgs...)
|
|
args = append(args, "-")
|
|
|
|
cmd := exec.Command(o.podmanCmd, args...)
|
|
klog.V(3).Infof("executing %v", cmd.Args)
|
|
stdin, err := cmd.StdinPipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.Stderr = cmd.Stdout
|
|
|
|
if err = cmd.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if klog.V(4) {
|
|
var sb strings.Builder
|
|
_ = serializer.Encode(pod, &sb)
|
|
klog.Infof("Pod spec to play: \n---\n%s\n---\n", sb.String())
|
|
}
|
|
|
|
err = serializer.Encode(pod, stdin)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stdin.Close()
|
|
var podmanOut string
|
|
go func() {
|
|
for {
|
|
tmp := make([]byte, 1024)
|
|
_, err = stdout.Read(tmp)
|
|
podmanOut += string(tmp)
|
|
klog.V(4).Info(string(tmp))
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
}()
|
|
if err = cmd.Wait(); err != nil {
|
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
|
err = fmt.Errorf("%s: %s\nComplete Podman output:\n%s", err, string(exiterr.Stderr), podmanOut)
|
|
}
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (o *PodmanCli) KubeGenerate(name string) (*corev1.Pod, error) {
|
|
serializer := jsonserializer.NewSerializerWithOptions(
|
|
jsonserializer.SimpleMetaFactory{},
|
|
scheme.Scheme,
|
|
scheme.Scheme,
|
|
jsonserializer.SerializerOptions{
|
|
Yaml: true,
|
|
},
|
|
)
|
|
|
|
cmd := exec.Command(o.podmanCmd, append(o.containerRunGlobalExtraArgs, "generate", "kube", name)...)
|
|
klog.V(3).Infof("executing %v", cmd.Args)
|
|
resultBytes, err := cmd.Output()
|
|
if err != nil {
|
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
|
err = fmt.Errorf("%s: %s", err, string(exiterr.Stderr))
|
|
}
|
|
return nil, err
|
|
}
|
|
var pod corev1.Pod
|
|
_, _, err = serializer.Decode(resultBytes, nil, &pod)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &pod, nil
|
|
}
|
|
|
|
func (o *PodmanCli) PodStop(podname string) error {
|
|
cmd := exec.Command(o.podmanCmd, append(o.containerRunGlobalExtraArgs, "pod", "stop", podname)...)
|
|
klog.V(3).Infof("executing %v", cmd.Args)
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
|
err = fmt.Errorf("%s: %s", err, string(exiterr.Stderr))
|
|
}
|
|
return err
|
|
}
|
|
klog.V(4).Infof("Stopped pod %s", string(out))
|
|
return nil
|
|
}
|
|
|
|
func (o *PodmanCli) PodRm(podname string) error {
|
|
cmd := exec.Command(o.podmanCmd, append(o.containerRunGlobalExtraArgs, "pod", "rm", podname)...)
|
|
klog.V(3).Infof("executing %v", cmd.Args)
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
|
err = fmt.Errorf("%s: %s", err, string(exiterr.Stderr))
|
|
}
|
|
return err
|
|
}
|
|
klog.V(4).Infof("Deleted pod %s", string(out))
|
|
return nil
|
|
}
|
|
|
|
func (o *PodmanCli) PodLs() (map[string]bool, error) {
|
|
cmd := exec.Command(o.podmanCmd, append(o.containerRunGlobalExtraArgs, "pod", "list", "--format", "{{.Name}}", "--noheading")...)
|
|
klog.V(3).Infof("executing %v", cmd.Args)
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
|
err = fmt.Errorf("%s: %s", err, string(exiterr.Stderr))
|
|
}
|
|
return nil, err
|
|
}
|
|
return SplitLinesAsSet(string(out)), nil
|
|
}
|
|
|
|
func (o *PodmanCli) VolumeRm(volumeName string) error {
|
|
cmd := exec.Command(o.podmanCmd, append(o.containerRunGlobalExtraArgs, "volume", "rm", volumeName)...)
|
|
klog.V(3).Infof("executing %v", cmd.Args)
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
|
err = fmt.Errorf("%s: %s", err, string(exiterr.Stderr))
|
|
}
|
|
return err
|
|
}
|
|
klog.V(4).Infof("Deleted volume %s", string(out))
|
|
return nil
|
|
}
|
|
|
|
func (o *PodmanCli) VolumeLs() (map[string]bool, error) {
|
|
cmd := exec.Command(o.podmanCmd, append(o.containerRunGlobalExtraArgs, "volume", "ls", "--format", "{{.Name}}", "--noheading")...)
|
|
klog.V(3).Infof("executing %v", cmd.Args)
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
|
err = fmt.Errorf("%s: %s", err, string(exiterr.Stderr))
|
|
}
|
|
return nil, err
|
|
}
|
|
return SplitLinesAsSet(string(out)), nil
|
|
}
|
|
|
|
func (o *PodmanCli) CleanupPodResources(pod *corev1.Pod, cleanupVolumes bool) error {
|
|
err := o.PodStop(pod.GetName())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = o.PodRm(pod.GetName())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !cleanupVolumes {
|
|
return nil
|
|
}
|
|
|
|
for _, volume := range pod.Spec.Volumes {
|
|
if volume.PersistentVolumeClaim == nil {
|
|
continue
|
|
}
|
|
volumeName := volume.PersistentVolumeClaim.ClaimName
|
|
klog.V(3).Infof("deleting podman volume %q", volumeName)
|
|
err = o.VolumeRm(volumeName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func SplitLinesAsSet(s string) map[string]bool {
|
|
lines := map[string]bool{}
|
|
sc := bufio.NewScanner(strings.NewReader(s))
|
|
for sc.Scan() {
|
|
lines[sc.Text()] = true
|
|
}
|
|
return lines
|
|
}
|