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")
}