Skip to content

Commit

Permalink
Move copyfiles function to client-go
Browse files Browse the repository at this point in the history
Resolves #443

This PR will remove dependency of oc binary for `copyfiles` function which copies file to component while `odo push`
  • Loading branch information
surajnarwade committed Jun 8, 2018
1 parent 3b5940e commit 36da172
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 246 deletions.
8 changes: 1 addition & 7 deletions pkg/component/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,17 +175,11 @@ func PushLocal(client *occlient.Client, componentName string, applicationName st
if err != nil {
return errors.Wrapf(err, "error while waiting for pod %s", podSelector)
}
var syncOutput string
if !asFile {
syncOutput, err = client.RsyncPath(path, pod.Name, targetPath)
} else {
syncOutput, err = client.CopyFile(path, pod.Name, targetPath)

}
err = client.CopyFile(asFile, path, pod.Name, targetPath)
if err != nil {
return errors.Wrap(err, "unable push files to pod")
}
fmt.Fprintf(out, syncOutput)
fmt.Fprintf(out, "Please wait, building component....\n")

// use pipes to write output from ExecCMDInContainer in yellow to 'out' io.Writer
Expand Down
268 changes: 119 additions & 149 deletions pkg/occlient/occlient.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package occlient

import (
taro "archive/tar"
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
Expand All @@ -24,20 +26,20 @@ import (
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"

scv1beta1 "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1"
servicecatalogclienset "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset/typed/servicecatalog/v1beta1"
appsv1 "github.com/openshift/api/apps/v1"
buildv1 "github.com/openshift/api/build/v1"
imagev1 "github.com/openshift/api/image/v1"
projectv1 "github.com/openshift/api/project/v1"
routev1 "github.com/openshift/api/route/v1"
appsclientset "github.com/openshift/client-go/apps/clientset/versioned/typed/apps/v1"
buildschema "github.com/openshift/client-go/build/clientset/versioned/scheme"
buildclientset "github.com/openshift/client-go/build/clientset/versioned/typed/build/v1"
imageclientset "github.com/openshift/client-go/image/clientset/versioned/typed/image/v1"
projectclientset "github.com/openshift/client-go/project/clientset/versioned/typed/project/v1"
routeclientset "github.com/openshift/client-go/route/clientset/versioned/typed/route/v1"

scv1beta1 "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1"
appsv1 "github.com/openshift/api/apps/v1"
buildv1 "github.com/openshift/api/build/v1"
imagev1 "github.com/openshift/api/image/v1"
projectv1 "github.com/openshift/api/project/v1"
routev1 "github.com/openshift/api/route/v1"
userclientset "github.com/openshift/client-go/user/clientset/versioned/typed/user/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -61,14 +63,14 @@ const (
)

type Client struct {
ocpath string
kubeClient kubernetes.Interface
imageClient imageclientset.ImageV1Interface
appsClient appsclientset.AppsV1Interface
buildClient buildclientset.BuildV1Interface
projectClient projectclientset.ProjectV1Interface
serviceCatalogClient servicecatalogclienset.ServicecatalogV1beta1Interface
routeClient routeclientset.RouteV1Interface
userClient userclientset.UserV1Interface
kubeConfig clientcmd.ClientConfig
namespace string
}
Expand Down Expand Up @@ -128,24 +130,23 @@ func New() (*Client, error) {
}
client.routeClient = routeClient

namespace, _, err := client.kubeConfig.Namespace()
userClient, err := userclientset.NewForConfig(config)
if err != nil {
return nil, err
}
client.namespace = namespace

// The following should go away once we're done with complete migration to
// client-go
ocpath, err := getOcBinary()
client.userClient = userClient

namespace, _, err := client.kubeConfig.Namespace()
if err != nil {
return nil, errors.Wrap(err, "unable to get oc binary")
return nil, err
}
client.ocpath = ocpath
client.namespace = namespace

if !isServerUp(client.ocpath) {
if !isServerUp(config.Host) {
return nil, errors.New("Unable to connect to OpenShift cluster, is it down?")
}
if !isLoggedIn(client.ocpath) {
if !client.isLoggedIn() {
return nil, errors.New("Please log in to the cluster")
}

Expand Down Expand Up @@ -202,99 +203,10 @@ func imageWithMetadata(image *imagev1.Image) error {
return nil
}

// getOcBinary returns full path to oc binary
// first it looks for env variable KUBECTL_PLUGINS_CALLER (run as oc plugin)
// than looks for env variable OC_BIN (set manualy by user)
// at last it tries to find oc in default PATH
func getOcBinary() (string, error) {
log.Debug("getOcBinary - searching for oc binary")

var ocPath string

envKubectlPluginCaller := os.Getenv("KUBECTL_PLUGINS_CALLER")
envOcBin := os.Getenv("OC_BIN")

log.Debugf("envKubectlPluginCaller = %s\n", envKubectlPluginCaller)
log.Debugf("envOcBin = %s\n", envOcBin)

if len(envKubectlPluginCaller) > 0 {
log.Debug("using path from KUBECTL_PLUGINS_CALLER")
ocPath = envKubectlPluginCaller
} else if len(envOcBin) > 0 {
log.Debug("using path from OC_BIN")
ocPath = envOcBin
} else {
path, err := exec.LookPath("oc")
if err != nil {
log.Debug("oc binary not found in PATH")
return "", err
}
log.Debug("using oc from PATH")
ocPath = path
}
log.Debug("using oc from %s", ocPath)

if _, err := os.Stat(ocPath); err != nil {
return "", err
}

return ocPath, nil
}

type OcCommand struct {
args []string
data *string
format string
}

// runOcCommands executes oc
// args - command line arguments to be passed to oc ('-o json' is added by default if data is not nil)
// data - is a pointer to a string, if set than data is given to command to stdin ('-f -' is added to args as default)
func (c *Client) runOcComamnd(command *OcCommand) ([]byte, error) {
cmd := exec.Command(c.ocpath, command.args...)

// if data is not set assume that it is get command
if len(command.format) > 0 {
cmd.Args = append(cmd.Args, "-o", command.format)
}
if command.data != nil {
// data is given, assume this is create or apply command
// that takes data from stdin
cmd.Args = append(cmd.Args, "-f", "-")

// Read from stdin
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, err
}

// Write to stdin
go func() {
defer stdin.Close()
_, err := io.WriteString(stdin, *command.data)
if err != nil {
fmt.Printf("can't write to stdin %v\n", err)
}
}()
}

log.Debugf("running oc command with arguments: %s\n", strings.Join(cmd.Args, " "))

output, err := cmd.CombinedOutput()
if err != nil {
if _, ok := err.(*exec.ExitError); ok {
return nil, errors.Wrapf(err, "command: %v failed to run:\n%v", cmd.Args, string(output))
}
return nil, errors.Wrap(err, "unable to get combined output")
}

return output, nil
}

func isLoggedIn(ocpath string) bool {
cmd := exec.Command(ocpath, "whoami")
output, err := cmd.CombinedOutput()
log.Debugf("isLoggedIn err: %#v \n output: %#v", err, string(output))
// isLoggedIn checks whether user is logged in or not and returns boolean output
func (c *Client) isLoggedIn() bool {
output, err := c.userClient.Users().Get("~", metav1.GetOptions{})
log.Debugf("isLoggedIn err: %#v \n output: %#v", err, output.Name)
if err != nil {
log.Debug(errors.Wrap(err, "error running command"))
log.Debugf("Output is: %v", output)
Expand All @@ -303,15 +215,8 @@ func isLoggedIn(ocpath string) bool {
return true
}

func isServerUp(ocpath string) bool {
cmd := exec.Command(ocpath, "whoami", "--show-server")
output, err := cmd.CombinedOutput()
if err != nil {
log.Debug(errors.Wrap(err, "error running command"))
return false
}

server := strings.TrimSpace(string(output))
// isServerUp returns true if server is up and running
func isServerUp(server string) bool {
u, err := url.Parse(server)
if err != nil {
log.Debug(errors.Wrap(err, "unable to parse url"))
Expand Down Expand Up @@ -1546,46 +1451,111 @@ func (c *Client) GetOnePodFromSelector(selector string) (*corev1.Pod, error) {
return &pods.Items[0], nil
}

// RsyncPath copies local directory to directory in running Pod.
func (c *Client) RsyncPath(localPath string, targetPodName string, targetPath string) (string, error) {
log.Debugf("Syncing %s to pod %s:%s", localPath, targetPodName, targetPath)
// CopyFile copies single local file to the directory in running Pod.
func (c *Client) CopyFile(asFile bool, localFile string, targetPodName string, targetPath string) error {
log.Debugf("Copying file %s to pod %s:%s", localFile, targetPodName, targetPath)

// TODO: do this without using 'oc' binary
args := []string{
"rsync",
localPath,
fmt.Sprintf("%s:%s", targetPodName, targetPath),
"--exclude", ".git",
"--no-perms",
}
dest := targetPath + "/" + path.Base(localFile)

reader, writer := io.Pipe()

go func() {
defer writer.Close()
err := makeTar(localFile, dest, writer)
if err != nil {
os.Exit(-1)
}

}()

var cmdArr []string

output, err := c.runOcComamnd(&OcCommand{args: args})
if !asFile {
cmdArr = []string{"tar", "xf", "-", "-C", targetPath, "--strip", "1"}
} else {
cmdArr = []string{"tar", "xf", "-", "-C", targetPath}
}
err := c.ExecCMDInContainer(targetPodName, cmdArr, writer, writer, reader, false)
if err != nil {
return "", err
return err
}

log.Debugf("command output:\n %s \n", string(output[:]))
return string(output[:]), nil
return nil
}

// CopyFile copies single local file to the directory in running Pod.
func (c *Client) CopyFile(localFile string, targetPodName string, targetPath string) (string, error) {
log.Debugf("Copying file %s to pod %s:%s", localFile, targetPodName, targetPath)
//makeTar function is copied from https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/cp.go#L309
func makeTar(srcPath, destPath string, writer io.Writer) error {
// TODO: use compression here?
tarWriter := taro.NewWriter(writer)
defer tarWriter.Close()

// TODO: do this without using 'oc' binary
args := []string{
"cp",
localFile,
fmt.Sprintf("%s:%s", targetPodName, targetPath),
}
srcPath = path.Clean(srcPath)
destPath = path.Clean(destPath)
return recursiveTar(path.Dir(srcPath), path.Base(srcPath), path.Dir(destPath), path.Base(destPath), tarWriter)
}

output, err := c.runOcComamnd(&OcCommand{args: args})
//recursiveTar function is copied from https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/cp.go#L319
func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *taro.Writer) error {
filepath := path.Join(srcBase, srcFile)
stat, err := os.Lstat(filepath)
if err != nil {
return "", err
return err
}
if stat.IsDir() {
files, err := ioutil.ReadDir(filepath)
if err != nil {
return err
}
if len(files) == 0 {
//case empty directory
hdr, _ := taro.FileInfoHeader(stat, filepath)
hdr.Name = destFile
if err := tw.WriteHeader(hdr); err != nil {
return err
}
}
for _, f := range files {
if err := recursiveTar(srcBase, path.Join(srcFile, f.Name()), destBase, path.Join(destFile, f.Name()), tw); err != nil {
return err
}
}
return nil
} else if stat.Mode()&os.ModeSymlink != 0 {
//case soft link
hdr, _ := taro.FileInfoHeader(stat, filepath)
target, err := os.Readlink(filepath)
if err != nil {
return err
}

hdr.Linkname = target
hdr.Name = destFile
if err := tw.WriteHeader(hdr); err != nil {
return err
}
} else {
//case regular file or other file type like pipe
hdr, err := taro.FileInfoHeader(stat, filepath)
if err != nil {
return err
}
hdr.Name = destFile

if err := tw.WriteHeader(hdr); err != nil {
return err
}

log.Debugf("command output:\n %s \n", string(output[:]))
return string(output[:]), nil
f, err := os.Open(filepath)
if err != nil {
return err
}
defer f.Close()

if _, err := io.Copy(tw, f); err != nil {
return err
}
return f.Close()
}
return nil
}

// GetOneServiceFromSelector returns the Service object associated with the
Expand Down

0 comments on commit 36da172

Please sign in to comment.