Home > Enterprise >  How do I copy a local file to a pod's container in my minikube cluster using the Go client?
How do I copy a local file to a pod's container in my minikube cluster using the Go client?

Time:01-02

My query is pretty much what the title says, I have a local file say file.txt and I want to copy it into pod1's container container1.

If I was to do it using kubectl, the appropriate command would be :

kubectl cp file.txt pod1:file.txt -c container1

However, how do I do it using the Go client of kubectl?

I tried 2 ways but none of them worked :

import (
    "fmt"
    "context"
    "log"
    "os"
    "path/filepath"

    g "github.com/sdslabs/katana/configs"
    v1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/labels"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/rest"
    "k8s.io/client-go/tools/clientcmd"
    //"k8s.io/kubectl/pkg/cmd/exec"
)

func CopyIntoPod(namespace string, podName string, containerName string, srcPath string, dstPath string) {
    // Create a Kubernetes client
    config, err := GetKubeConfig()
    if err != nil {
        log.Fatal(err)
    }

    client, err := kubernetes.NewForConfig(config)
    if err != nil {
        log.Fatal(err)
    }

    // Build the command to execute
    cmd := []string{"cp", srcPath, dstPath}

    // Use the PodExecOptions struct to specify the options for the exec request
    options := v1.PodExecOptions{
        Container: containerName,
        Command:   cmd,
        Stdin:     false,
        Stdout:    true,
        Stderr:    true,
        TTY:       false,
    }
    log.Println("Options set!")

    // Use the CoreV1Api.Exec method to execute the command inside the container
    req := client.CoreV1().RESTClient().Post().
        Namespace(namespace).
        Name(podName).
        Resource("pods").
        SubResource("exec").
        VersionedParams(&options, metav1.ParameterCodec)
    log.Println("Request generated")
    
    exec, err := req.Stream(context.TODO())
    if err != nil {
        log.Fatal(err)
    }
    defer exec.Close()

    // Read the response from the exec command
    var result []byte
    if _, err := exec.Read(result); err != nil {
        log.Fatal(err)
    }

    fmt.Println("File copied successfully!")
}

This gave me the error message :

no kind is registered for the type v1.PodExecOptions in scheme "pkg/runtime/scheme.go:100"

I couldn't figure it out, so I tried another way :

type PodExec struct {
    RestConfig *rest.Config
    *kubernetes.Clientset
}

func NewPodExec(config *rest.Config, clientset *kubernetes.Clientset) *PodExec {
    config.APIPath = "/api" // Make sure we target /api and not just /
    config.GroupVersion = &schema.GroupVersion{Version: "v1"} // this targets the core api groups so the url path will be /api/v1
    config.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}
    return &PodExec{
      RestConfig: config,
      Clientset:  clientset,
    }  
}

func (p *PodExec) PodCopyFile(src string, dst string, containername string, podNamespace string) (*bytes.Buffer, *bytes.Buffer, *bytes.Buffer, error) {
    ioStreams, in, out, errOut := genericclioptions.NewTestIOStreams()
    copyOptions := cp.NewCopyOptions(ioStreams)
    copyOptions.Clientset = p.Clientset
    copyOptions.ClientConfig = p.RestConfig
    copyOptions.Container = containername
    copyOptions.Namespace = podNamespace
    err := copyOptions.Run()
    if err != nil {
        return nil, nil, nil, fmt.Errorf("could not run copy operation: %v", err)
    }
    return in, out, errOut, nil
}

However, there were some issues with the copyOptions.Run() command, it tried to look for o.args[0] and o.args[0] inside copyOptions but o is not imported so it couldn't be modified.

Context : https://pkg.go.dev/k8s.io/kubectl/pkg/cmd/cp#CopyOptions.Run

So, now I'm really lost and confused. Any help would be appreciated. Thanks.

Edit : I did think of a viable method where we can just call cmd.exec() and run the kubectl cp command directly but it seems kinda hacky and I'm not sure whether it would work, any thoughts?

CodePudding user response:

Here's how I finally managed to do it :

package main

import (
    "context"
    "fmt"
    "os"
    "path/filepath"

    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/kubernetes/scheme"
    corev1 "k8s.io/api/core/v1"
    "k8s.io/client-go/tools/clientcmd"
    "k8s.io/client-go/util/homedir"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/tools/remotecommand"
)

func CopyIntoPod(podName string, namespace string, containerName string, srcPath string, dstPath string) {
    // Get the default kubeconfig file
    kubeConfig := filepath.Join(homedir.HomeDir(), ".kube", "config")

    // Create a config object using the kubeconfig file
    config, err := clientcmd.BuildConfigFromFlags("", kubeConfig)
    if err != nil {
        fmt.Printf("Error creating config: %s\n", err)
        return
    }

    // Create a Kubernetes client
    client, err := kubernetes.NewForConfig(config)
    if err != nil {
        fmt.Printf("Error creating client: %s\n", err)
        return
    }

    // Open the file to copy
    localFile, err := os.Open(srcPath)
    if err != nil {
        fmt.Printf("Error opening local file: %s\n", err)
        return
    }
    defer localFile.Close()

    pod, err := client.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
    if err != nil {
        fmt.Printf("Error getting pod: %s\n", err)
        return
    }

    // Find the container in the pod
    var container *corev1.Container
    for _, c := range pod.Spec.Containers {
        if c.Name == containerName {
            container = &c
            break
        }
    }

    if container == nil {
        fmt.Printf("Container not found in pod\n")
        return
    }

    // Create a stream to the container
    req := client.CoreV1().RESTClient().Post().
        Resource("pods").
        Name(podName).
        Namespace(namespace).
        SubResource("exec").
        Param("container", containerName)   

    req.VersionedParams(&corev1.PodExecOptions{
        Container: containerName,
        Command:   []string{"bash", "-c", "cat > "   dstPath},
        Stdin:     true,
        Stdout:    true,
        Stderr:    true,
    }, scheme.ParameterCodec)

    exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
    if err != nil {
        fmt.Printf("Error creating executor: %s\n", err)
        return
    }

    // Create a stream to the container
    err = exec.StreamWithContext(context.TODO(), remotecommand.StreamOptions{
        Stdin:  localFile,
        Stdout: os.Stdout,
        Stderr: os.Stderr,
        Tty:    false,
    })
    if err != nil {
        fmt.Printf("Error streaming: %s\n", err)
        return
    }

    fmt.Println("File copied successfully")
}
  • Related