'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