'How to copy file to container with kubernetes client-go?
I want to use https://github.com/kubernetes/client-go to copy a file from my file system to a container and vice versa.
kubectl cp <file-spec-src> <file-spec-dest> -c <specific-container>
Is there a function in the go client that wraps the calls? Or can I use something like the RESTClient?
Solution 1:[1]
There is the code using client-go realizes copying file to container, also counld copying file from container.
https://github.com/ica10888/client-go-helper/blob/master/pkg/kubectl/cp.go
??kubectl
? client.go
? cp.go
?? stub.s
client
package kubectl
import (
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
func InitRestClient() (*rest.Config, error, *corev1client.CoreV1Client) {
// Instantiate loader for kubeconfig file.
kubeconfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
clientcmd.NewDefaultClientConfigLoadingRules(),
&clientcmd.ConfigOverrides{},
)
// Get a rest.Config from the kubeconfig file. This will be passed into all
// the client objects we create.
restconfig, err := kubeconfig.ClientConfig()
if err != nil {
panic(err)
}
// Create a Kubernetes core/v1 client.
coreclient, err := corev1client.NewForConfig(restconfig)
if err != nil {
panic(err)
}
return restconfig, err, coreclient
}
copyToPod and copyFromPod
package kubectl
import (
"archive/tar"
"fmt"
"io"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/remotecommand"
_ "k8s.io/kubectl/pkg/cmd/cp"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"log"
"os"
"path"
"path/filepath"
"strings"
_ "unsafe"
)
func (i *pod) copyToPod(srcPath string, destPath string) error {
restconfig, err, coreclient := InitRestClient()
reader, writer := io.Pipe()
if destPath != "/" && strings.HasSuffix(string(destPath[len(destPath)-1]), "/") {
destPath = destPath[:len(destPath)-1]
}
if err := checkDestinationIsDir(i, destPath); err == nil {
destPath = destPath + "/" + path.Base(srcPath)
}
go func() {
defer writer.Close()
err := cpMakeTar(srcPath, destPath, writer)
cmdutil.CheckErr(err)
}()
var cmdArr []string
cmdArr = []string{"tar", "-xf", "-"}
destDir := path.Dir(destPath)
if len(destDir) > 0 {
cmdArr = append(cmdArr, "-C", destDir)
}
//remote shell.
req := coreclient.RESTClient().
Post().
Namespace(i.Namespace).
Resource("pods").
Name(i.Name).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: i.ContainerName,
Command: cmdArr,
Stdin: true,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(restconfig, "POST", req.URL())
if err != nil {
log.Fatalf("error %s\n", err)
return err
}
err = exec.Stream(remotecommand.StreamOptions{
Stdin: reader,
Stdout: os.Stdout,
Stderr: os.Stderr,
Tty: false,
})
if err != nil {
log.Fatalf("error %s\n", err)
return err
}
return nil
}
func checkDestinationIsDir(i *pod, destPath string) error {
return i.Exec([]string{"test", "-d", destPath})
}
//go:linkname cpMakeTar k8s.io/kubectl/pkg/cmd/cp.makeTar
func cpMakeTar(srcPath, destPath string, writer io.Writer) error
func (i *pod) copyFromPod(srcPath string, destPath string) error {
restconfig, err, coreclient := InitRestClient()
reader, outStream := io.Pipe()
//todo some containers failed : tar: Refusing to write archive contents to terminal (missing -f option?) when execute `tar cf -` in container
cmdArr := []string{"tar", "cf", "-", srcPath}
req := coreclient.RESTClient().
Get().
Namespace(i.Namespace).
Resource("pods").
Name(i.Name).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: i.ContainerName,
Command: cmdArr,
Stdin: true,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(restconfig, "POST", req.URL())
if err != nil {
log.Fatalf("error %s\n", err)
return err
}
go func() {
defer outStream.Close()
err = exec.Stream(remotecommand.StreamOptions{
Stdin: os.Stdin,
Stdout: outStream,
Stderr: os.Stderr,
Tty: false,
})
cmdutil.CheckErr(err)
}()
prefix := getPrefix(srcPath)
prefix = path.Clean(prefix)
prefix = cpStripPathShortcuts(prefix)
destPath = path.Join(destPath, path.Base(prefix))
err = untarAll(reader, destPath, prefix)
return err
}
func untarAll(reader io.Reader, destDir, prefix string) error {
tarReader := tar.NewReader(reader)
for {
header, err := tarReader.Next()
if err != nil {
if err != io.EOF {
return err
}
break
}
if !strings.HasPrefix(header.Name, prefix) {
return fmt.Errorf("tar contents corrupted")
}
mode := header.FileInfo().Mode()
destFileName := filepath.Join(destDir, header.Name[len(prefix):])
baseName := filepath.Dir(destFileName)
if err := os.MkdirAll(baseName, 0755); err != nil {
return err
}
if header.FileInfo().IsDir() {
if err := os.MkdirAll(destFileName, 0755); err != nil {
return err
}
continue
}
evaledPath, err := filepath.EvalSymlinks(baseName)
if err != nil {
return err
}
if mode&os.ModeSymlink != 0 {
linkname := header.Linkname
if !filepath.IsAbs(linkname) {
_ = filepath.Join(evaledPath, linkname)
}
if err := os.Symlink(linkname, destFileName); err != nil {
return err
}
} else {
outFile, err := os.Create(destFileName)
if err != nil {
return err
}
defer outFile.Close()
if _, err := io.Copy(outFile, tarReader); err != nil {
return err
}
if err := outFile.Close(); err != nil {
return err
}
}
}
return nil
}
func getPrefix(file string) string {
return strings.TrimLeft(file, "/")
}
//go:linkname cpStripPathShortcuts k8s.io/kubectl/pkg/cmd/cp.stripPathShortcuts
func cpStripPathShortcuts(p string) string
touch stub.s
Solution 2:[2]
Since the answer to this is pretty old, here is how I did it :
package main
import (
"bytes"
"fmt"
"io"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/kubectl/pkg/cmd/cp"
"k8s.io/kubectl/pkg/cmd/exec"
"log"
"os"
)
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) (*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
err := copyOptions.Run([]string{src, dst})
if err != nil {
return nil, nil, nil, fmt.Errorf("Could not run copy operation: %v", err)
}
return in, out, errOut, nil
}
You can then use PodCopyFile just like kubectl cp
podExec := podexec.NewPodExec(*restconfig, clientset) // Here, you need to get your restconfig and clientset from either ~/.kube/config or built-in pod config.
_, out, _, err := podExec.PodCopyFile("/srcfile", "/dstfile", "containername")
if err != nil {
fmt.Printf("%v\n", err)
}
fmt.Println("out:")
fmt.Printf("%s", out.String())
Solution 3:[3]
Ironically, someone just today upvoted my answer to this question for Java. I haven't opened the client-go repo to have a look, but I would be very, very surprised if it exposes the cp command any more than the Java library does.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Avinash Dhinwa |
| Solution 2 | |
| Solution 3 | mdaniel |
