Merge pull request #109 from justinsb/upup_addons

upup: experimental addon management
This commit is contained in:
Justin Santa Barbara 2016-06-13 11:37:07 -04:00 committed by GitHub
commit 0d617c7ffb
14 changed files with 1004 additions and 15 deletions

View File

@ -0,0 +1,41 @@
# This file should be kept in sync with cluster/images/hyperkube/dashboard-rc.yaml
# and cluster/gce/coreos/kube-manifests/addons/dashboard/dashboard-controller.yaml
apiVersion: v1
kind: ReplicationController
metadata:
name: kubernetes-dashboard-v1.1.0-beta2
namespace: kube-system
labels:
k8s-app: kubernetes-dashboard
version: v1.1.0-beta2
kubernetes.io/cluster-service: "true"
spec:
replicas: 1
selector:
k8s-app: kubernetes-dashboard
template:
metadata:
labels:
k8s-app: kubernetes-dashboard
version: v1.1.0-beta2
kubernetes.io/cluster-service: "true"
spec:
containers:
- name: kubernetes-dashboard
image: gcr.io/google_containers/kubernetes-dashboard-amd64:v1.1.0-beta2
resources:
# keep request = limit to keep this container in guaranteed class
limits:
cpu: 100m
memory: 50Mi
requests:
cpu: 100m
memory: 50Mi
ports:
- containerPort: 9090
livenessProbe:
httpGet:
path: /
port: 9090
initialDelaySeconds: 30
timeoutSeconds: 30

View File

@ -0,0 +1,16 @@
# This file should be kept in sync with cluster/images/hyperkube/dashboard-svc.yaml
# and cluster/gce/coreos/kube-manifests/addons/dashboard/dashboard-service.yaml
apiVersion: v1
kind: Service
metadata:
name: kubernetes-dashboard
namespace: kube-system
labels:
k8s-app: kubernetes-dashboard
kubernetes.io/cluster-service: "true"
spec:
selector:
k8s-app: kubernetes-dashboard
ports:
- port: 80
targetPort: 9090

View File

@ -0,0 +1,63 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: heapster-v1.1.0.beta2
namespace: kube-system
labels:
k8s-app: heapster
kubernetes.io/cluster-service: "true"
version: v1.1.0.beta2
spec:
replicas: 1
selector:
matchLabels:
k8s-app: heapster
version: v1.1.0.beta2
template:
metadata:
labels:
k8s-app: heapster
version: v1.1.0.beta2
spec:
containers:
- image: gcr.io/google_containers/heapster:v1.1.0-beta2
name: heapster
resources:
# keep request = limit to keep this container in guaranteed class
limits:
cpu: 100m
memory: 300Mi
requests:
cpu: 100m
memory: 300Mi
command:
- /heapster
- --source=kubernetes.summary_api:''
- image: gcr.io/google_containers/addon-resizer:1.0
name: heapster-nanny
resources:
limits:
cpu: 50m
memory: 100Mi
requests:
cpu: 50m
memory: 100Mi
env:
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
command:
- /pod_nanny
- --cpu=100m
- --extra-cpu=0m
- --memory=200Mi
- --extra-memory=4Mi
- --threshold=5
- --deployment=heapster-v1.1.0.beta2
- --container=heapster
- --poll-period=300000

View File

@ -0,0 +1,14 @@
kind: Service
apiVersion: v1
metadata:
name: heapster
namespace: kube-system
labels:
kubernetes.io/cluster-service: "true"
kubernetes.io/name: "Heapster"
spec:
ports:
- port: 80
targetPort: 8082
selector:
k8s-app: heapster

94
upup/cmd/upup/addons.go Normal file
View File

@ -0,0 +1,94 @@
package main
import (
"fmt"
"github.com/spf13/cobra"
"k8s.io/kube-deploy/upup/pkg/kutil"
"strings"
"github.com/golang/glog"
"encoding/json"
)
// AddonsCmd represents the addons command
type AddonsCmd struct {
//ClusterName string
cobraCommand *cobra.Command
}
var addonsCmd = AddonsCmd{
cobraCommand: &cobra.Command{
Use: "addons",
Short: "manage cluster addons",
Long: `manage cluster addons`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Usage: addons get")
},
},
}
func init() {
cmd := addonsCmd.cobraCommand
rootCommand.cobraCommand.AddCommand(cmd)
//cmd.PersistentFlags().StringVar(&addonsCmd.ClusterName, "name", "", "cluster name")
}
type kubectlConfig struct {
Kind string `json:"kind`
ApiVersion string `json:"apiVersion`
Clusters []*kubectlClusterWithName `json:"clusters`
}
type kubectlClusterWithName struct {
Name string `json:"name`
Cluster kubectlCluster `json:"cluster`
}
type kubectlCluster struct {
Server string `json:"server`
}
func (c *AddonsCmd) buildClusterAddons() (*kutil.ClusterAddons, error) {
//if c.ClusterName == "" {
// return fmt.Errorf("--name is required")
//}
kubectl := &kutil.Kubectl{}
//context, err := kubectl.GetCurrentContext()
//if err != nil {
// return nil, fmt.Errorf("error getting current context from kubectl: %v", err)
//}
//glog.V(4).Infof("context = %q", context)
configString, err := kubectl.GetConfig(true, "json")
if err != nil {
return nil, fmt.Errorf("error getting current config from kubectl: %v", err)
}
glog.V(8).Infof("config = %q", configString)
config := &kubectlConfig{}
err = json.Unmarshal([]byte(configString), config)
if err != nil {
return nil, fmt.Errorf("cannot parse current config from kubectl: %v", err)
}
if len(config.Clusters) != 1 {
return nil, fmt.Errorf("expected exactly one cluster in kubectl config, found %d", len(config.Clusters))
}
namedCluster := config.Clusters[0]
glog.V(4).Infof("using cluster name %q", namedCluster.Name)
server := namedCluster.Cluster.Server
server = strings.TrimSpace(server)
if server == "" {
return nil, fmt.Errorf("server was not set in kubectl config")
}
k := &kutil.ClusterAddons{
APIEndpoint: server,
}
return k, nil
}

View File

@ -0,0 +1,71 @@
package main
import (
"fmt"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kube-deploy/upup/pkg/kutil"
"k8s.io/kube-deploy/upup/pkg/fi/vfs"
)
type AddonsCreateCmd struct {
cobraCommand *cobra.Command
}
var addonsCreateCmd = AddonsCreateCmd{
cobraCommand: &cobra.Command{
Use: "create",
Short: "Create an addons",
Long: `Create an addon in a cluster.`,
},
}
func init() {
cmd := addonsCreateCmd.cobraCommand
addonsCmd.cobraCommand.AddCommand(cmd)
cmd.Run = func(cmd *cobra.Command, args []string) {
err := addonsCreateCmd.Run(args)
if err != nil {
glog.Exitf("%v", err)
}
}
}
func (c *AddonsCreateCmd) Run(args []string) error {
k, err := addonsCmd.buildClusterAddons()
if err != nil {
return err
}
privateKeyFile := expandPath("~/.ssh/id_rsa")
err = kutil.AddSSHIdentity(&k.SSHConfig, privateKeyFile)
if err != nil {
return fmt.Errorf("error adding SSH private key %q: %v", err)
}
addonFiles := make(map[string][]vfs.Path)
for _, path := range args {
vfsPath := vfs.NewFSPath(path)
files, err := vfsPath.ReadDir()
if err != nil {
return fmt.Errorf("error listing path %s: %v", vfsPath, err)
}
key := vfsPath.Base()
addonFiles[key] = files
}
for key, files := range addonFiles {
glog.Infof("Creating addon %q", key)
err := k.CreateAddon(key, files)
if err != nil {
return fmt.Errorf("error creating addon %q: %v", key, err)
}
}
return nil
}

View File

@ -0,0 +1,91 @@
package main
import (
"fmt"
"bytes"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kube-deploy/upup/pkg/kutil"
"os"
"text/tabwriter"
)
type AddonsGetCmd struct {
ClusterName string
cobraCommand *cobra.Command
}
var addonsGetCmd = AddonsGetCmd{
cobraCommand: &cobra.Command{
Use: "get",
Short: "Display one or many addons",
Long: `Query a cluster, and list the addons.`,
},
}
func init() {
cmd := addonsGetCmd.cobraCommand
addonsCmd.cobraCommand.AddCommand(cmd)
cmd.Flags().StringVar(&addonsGetCmd.ClusterName, "name", "", "cluster name")
cmd.Run = func(cmd *cobra.Command, args []string) {
err := addonsGetCmd.Run()
if err != nil {
glog.Exitf("%v", err)
}
}
}
func (c *AddonsGetCmd) Run() error {
k, err := addonsCmd.buildClusterAddons()
if err != nil {
return err
}
privateKeyFile := expandPath("~/.ssh/id_rsa")
err = kutil.AddSSHIdentity(&k.SSHConfig, privateKeyFile)
if err != nil {
return fmt.Errorf("error adding SSH private key %q: %v", err)
}
addons, err := k.ListAddons()
if err != nil {
return err
}
err = c.printAddons(addons)
if err != nil {
return err
}
return nil
}
func (c *AddonsGetCmd) printAddons(addons map[string]*kutil.ClusterAddon) error {
w := new(tabwriter.Writer)
var b bytes.Buffer
// Format in tab-separated columns with a tab stop of 8.
w.Init(os.Stdout, 0, 8, 0, '\t', tabwriter.StripEscape)
for _, n := range addons {
b.WriteByte(tabwriter.Escape)
b.WriteString(n.Name)
b.WriteByte(tabwriter.Escape)
b.WriteByte('\t')
b.WriteByte(tabwriter.Escape)
b.WriteString(n.Path)
b.WriteByte(tabwriter.Escape)
b.WriteByte('\n')
_, err := w.Write(b.Bytes())
if err != nil {
return fmt.Errorf("error writing to output: %v", err)
}
b.Reset()
}
return w.Flush()
}

14
upup/cmd/upup/utils.go Normal file
View File

@ -0,0 +1,14 @@
package main
import (
"strings"
"os"
)
// expandPath replaces common path aliases: ~ -> $HOME
func expandPath(p string) (string) {
if strings.HasPrefix(p, "~/") {
p = os.Getenv("HOME") + p[1:]
}
return p
}

38
upup/glide.lock generated
View File

@ -1,8 +1,8 @@
hash: a9f93fedfb45e32b891cdf1f53c475acff2dd80fc02577b7c1d4da4e099bc450
updated: 2016-06-07T16:08:47.827037549-04:00
hash: 42a551da194c2a8dfcaff364d22c71359cac802ad0ede68b4cd574b7fdebb9e1
updated: 2016-06-13T00:14:13.556460446-04:00
imports:
- name: github.com/aws/aws-sdk-go
version: 035c9cf82d27ec7e191fe9140158ab57f82cc0e3
version: 64c371362a7b5ecafa2d7cdf90d19aba7343ae02
subpackages:
- aws
- aws/awserr
@ -12,6 +12,7 @@ imports:
- service/iam
- service/route53
- aws/session
- service/s3
- aws/credentials
- aws/awsutil
- aws/client
@ -48,28 +49,35 @@ imports:
- name: github.com/golang/glog
version: 23def4e6c14b4da8ac2ed8007337bc5eb5007998
- name: github.com/golang/protobuf
version: 3b06fc7a4cad73efce5fe6217ab6c33e7231ab4a
version: 8616e8ee5e20a1704615e6c8d7afcdac06087a67
subpackages:
- proto
- name: github.com/hashicorp/hcl
version: d7400db7143f8e869812e50a53acd6c8d92af3b8
subpackages:
- hcl/ast
- hcl/printer
- json/parser
- hcl/parser
- hcl/token
- json/parser
- hcl/scanner
- hcl/strconv
- json/scanner
- json/token
- hcl/scanner
- hcl/strconv
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/jmespath/go-jmespath
version: 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74
- name: github.com/kr/fs
version: 2788f0dbd16903de03cb8186e5c7d97b69ad387b
- name: github.com/magiconair/properties
version: c265cfa48dda6474e208715ca93e987829f572f8
- name: github.com/mitchellh/mapstructure
version: d2dd0262208475919e1a362f675cfc0e7c10e905
- name: github.com/pkg/errors
version: 01fa4104b9c248c8945d14d9f128454d5b28d595
- name: github.com/pkg/sftp
version: 8ceba579e780069dc5b5c866c0b9fa88fb736a4a
- name: github.com/spf13/cast
version: 27b586b42e29bec072fe7379259cc719e1289da6
- name: github.com/spf13/cobra
@ -77,30 +85,30 @@ imports:
- name: github.com/spf13/jwalterweatherman
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
- name: github.com/spf13/pflag
version: cb88ea77998c3f024757528e3305022ab50b43be
version: 367864438f1b1a3c7db4da06a2f55b144e6784e0
- name: github.com/spf13/viper
version: c1ccc378a054ea8d4e38d8c67f6938d4760b53dd
- name: golang.org/x/crypto
version: 77f4136a99ffb5ecdbdd0226bd5cb146cf56bc0e
version: f3241ce8505855877cc8a9717bd61a0f7c4ea83c
subpackages:
- ssh
- curve25519
- ed25519
- ed25519/internal/edwards25519
- name: golang.org/x/net
version: 154d9f9ea81208afed560f4cf27b4860c8ed1904
version: 3f122ce3dbbe488b7e6a8bdb26f41edec852a40b
subpackages:
- context
- context/ctxhttp
- name: golang.org/x/oauth2
version: 71d9edd725fe4ce4c692fcb20765be558df45ad3
version: 65a8d08c6292395d47053be10b3c5e91960def76
subpackages:
- google
- internal
- jws
- jwt
- name: golang.org/x/sys
version: 076b546753157f758b316e59bcb51e6807c04057
version: 7f918dd405547ecb864d14a8ecbbfe205b5f930f
subpackages:
- unix
- name: google.golang.org/api
@ -123,16 +131,16 @@ imports:
- internal/datastore
- internal/remote_api
- name: google.golang.org/cloud
version: 20e786f4db50de279a834da101b142620cfb2a41
version: 4a23f97e60c9a14de1269e78812e59ca94033d85
subpackages:
- compute/metadata
- internal
- name: google.golang.org/grpc
version: b60d3e9ed84da5710af43964e78bc44de74b72aa
version: 88aeffff979aa77aa502cb011423d0a08fa12c5a
- name: gopkg.in/yaml.v2
version: a83829b6f1293c91addabc89d0571c246397bbf4
- name: k8s.io/kubernetes
version: 5b7e617abfff8c81921114e730f3fcbb47dfd1fd
version: 5d6397e9ee610664387fd9a45238f9af6fa9a18e
subpackages:
- pkg/util/diff
- pkg/util/exec

View File

@ -23,3 +23,4 @@ import:
- ssh
- package: github.com/cloudfoundry-incubator/candiedyaml
- package: github.com/spf13/cobra
- package: github.com/pkg/sftp

308
upup/pkg/fi/vfs/sshfs.go Normal file
View File

@ -0,0 +1,308 @@
package vfs
import (
"golang.org/x/crypto/ssh"
"path"
"github.com/pkg/sftp"
"github.com/golang/glog"
"fmt"
"os"
"bytes"
"sync"
"math/rand"
"io"
)
type SSHPath struct {
client *ssh.Client
sudo bool
server string
path string
}
var _ Path = &SSHPath{}
func NewSSHPath(client *ssh.Client, server string, path string, sudo bool) *SSHPath {
return &SSHPath{
client: client,
server: server,
path: path,
sudo: sudo,
}
}
func (p*SSHPath) newClient() (*sftp.Client, error) {
if !p.sudo {
sftpClient, err := sftp.NewClient(p.client)
if err != nil {
return nil, fmt.Errorf("error creating sftp client: %v", err)
}
return sftpClient, nil
} else {
s, err := p.client.NewSession()
if err != nil {
return nil, fmt.Errorf("error creating sftp client (in new-session): %v", err)
}
//if err := s.R("sftp"); err != nil {
// return nil, fmt.Errorf("error creating sftp client (in new-session): %v", err)
//}
stdin, err := s.StdinPipe()
if err != nil {
return nil, fmt.Errorf("error creating sftp client (at stdin pipe): %v", err)
}
stdout, err := s.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("error creating sftp client (at stdout pipe): %v", err)
}
err = s.Start("sudo /usr/lib/openssh/sftp-server")
if err != nil {
return nil, fmt.Errorf("error creating sftp client (executing 'sudo /usr/lib/openssh/sftp-server'): %v", err)
}
return sftp.NewClientPipe(stdout, stdin)
}
}
func (p *SSHPath) String() string {
return "ssh://" + p.server + p.path
}
func (p *SSHPath) Join(relativePath ...string) Path {
args := []string{p.path}
args = append(args, relativePath...)
joined := path.Join(args...)
return NewSSHPath(p.client, p.server, joined, p.sudo)
}
func mkdirAll(sftpClient *sftp.Client, dir string) error {
if dir == "/" {
// Must always exist
return nil
}
stat, err := sftpClient.Lstat(dir)
if err == nil {
if !stat.IsDir() {
return fmt.Errorf("not a directory: %q", dir)
}
return nil
}
parent := path.Dir(dir)
err = mkdirAll(sftpClient, parent)
if err != nil {
return err
}
err = sftpClient.Mkdir(dir)
if err != nil {
return fmt.Errorf("error creating directory %q over sftp: %v", dir, err)
}
return nil
}
func (p *SSHPath) WriteFile(data []byte) error {
sftpClient, err := p.newClient()
if err != nil {
return err
}
defer sftpClient.Close()
dir := path.Dir(p.path)
err = mkdirAll(sftpClient, dir)
if err != nil {
return err
}
tempfile := path.Join(dir, fmt.Sprintf(".tmp-%d", rand.Int63()))
f, err := sftpClient.Create(tempfile)
if err != nil {
// TODO: Retry if concurrently created?
return fmt.Errorf("error creating temp file in %q: %v", dir, err)
}
// Note from here on in we have to close f and delete or rename the temp file
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
if closeErr := f.Close(); err == nil {
err = closeErr
}
if err == nil {
err = sftpClient.Rename(tempfile, p.path)
if err != nil {
err = fmt.Errorf("error during file write of %q: rename failed: %v", p.path, err)
}
}
if err == nil {
return nil
}
// Something went wrong; try to remove the temp file
if removeErr := sftpClient.Remove(tempfile); removeErr != nil {
glog.Warningf("unable to remove temp file %q: %v", tempfile, removeErr)
}
return err
}
// To prevent concurrent creates on the same file while maintaining atomicity of writes,
// we take a process-wide lock during the operation.
// Not a great approach, but fine for a single process (with low concurrency)
var createFileLockSSH sync.Mutex
func (p *SSHPath) CreateFile(data []byte) error {
createFileLockSSH.Lock()
defer createFileLockSSH.Unlock()
// Check if exists
_, err := p.ReadFile()
if err == nil {
return os.ErrExist
}
if !os.IsNotExist(err) {
return err
}
return p.WriteFile(data)
}
func (p *SSHPath) ReadFile() ([]byte, error) {
sftpClient, err := p.newClient()
if err != nil {
return nil, err
}
defer sftpClient.Close()
f, err := sftpClient.Open(p.path)
if err != nil {
return nil, fmt.Errorf("error opening file %s over sftp: %v", p, err)
}
defer f.Close()
var b bytes.Buffer
_, err = f.WriteTo(&b)
if err != nil {
return nil, fmt.Errorf("error reading file %s over sftp: %v", p, err)
}
return b.Bytes(), nil
}
func (p *SSHPath) ReadDir() ([]Path, error) {
sftpClient, err := p.newClient()
if err != nil {
return nil, err
}
defer sftpClient.Close()
files, err := sftpClient.ReadDir(p.path)
if err != nil {
return nil, err
}
var children []Path
for _, f := range files {
child := NewSSHPath(p.client, p.server, path.Join(p.path, f.Name()), p.sudo)
children = append(children, child)
}
return children, nil
}
func (p *SSHPath) Base() string {
return path.Base(p.path)
}
//
//// scpMkdir executes a mkdir against the SSH target, using SCP
//func (s *SSHPath) scpMkdir(dest string, mode os.FileMode) error {
// glog.V(4).Infof("Doing SSH SCP mkdir: %q", dest)
// session, err := s.client.NewSession()
// if err != nil {
// return fmt.Errorf("error establishing SSH session: %v", err)
// }
// defer session.Close()
//
// name := path.Base(dest)
// scpBase := path.Dir(dest)
// //scpBase = "." + scpBase
//
// var stdinErr error
// go func() {
// w, _ := session.StdinPipe()
// defer w.Close()
// _, stdinErr = fmt.Fprintln(w, "D0"+toOctal(mode), 0, name)
// if stdinErr != nil {
// return
// }
// }()
// output, err := session.CombinedOutput("/usr/bin/scp -tr " + scpBase)
// if err != nil {
// glog.Warningf("Error output from SCP: %s", output)
// return fmt.Errorf("error doing SCP mkdir: %v", err)
// }
// if stdinErr != nil {
// glog.Warningf("Error output from SCP: %s", output)
// return fmt.Errorf("error doing SCP mkdir (writing to stdin): %v", stdinErr)
// }
//
// return nil
//}
//
//func toOctal(mode os.FileMode) string {
// return strconv.FormatUint(uint64(mode), 8)
//}
//
//// scpPut copies a file to the SSH target, using SCP
//func (s *SSHPath) scpPut(dest string, length int, content io.Reader, mode os.FileMode) error {
// glog.V(4).Infof("Doing SSH SCP upload: %q", dest)
// session, err := s.client.NewSession()
// if err != nil {
// return fmt.Errorf("error establishing SSH session: %v", err)
// }
// defer session.Close()
//
// name := path.Base(dest)
// scpBase := path.Dir(dest)
// //scpBase = "." + scpBase
//
// var stdinErr error
// go func() {
// w, _ := session.StdinPipe()
// defer w.Close()
// _, stdinErr = fmt.Fprintln(w, "C0"+toOctal(mode), length, name)
// if stdinErr != nil {
// return
// }
// _, stdinErr = io.Copy(w, content)
// if stdinErr != nil {
// return
// }
// _, stdinErr = fmt.Fprint(w, "\x00")
// if stdinErr != nil {
// return
// }
// }()
// output, err := session.CombinedOutput("/usr/bin/scp -tr " + scpBase)
// if err != nil {
// glog.Warningf("Error output from SCP: %s", output)
// return fmt.Errorf("error doing SCP put: %v", err)
// }
// if stdinErr != nil {
// glog.Warningf("Error output from SCP: %s", output)
// return fmt.Errorf("error doing SCP put (writing to stdin): %v", stdinErr)
// }
//
// return nil
//}

102
upup/pkg/kutil/addons.go Normal file
View File

@ -0,0 +1,102 @@
package kutil
import (
"fmt"
"golang.org/x/crypto/ssh"
"k8s.io/kube-deploy/upup/pkg/fi/vfs"
"strings"
)
type ClusterAddons struct {
APIEndpoint string
SSHConfig ssh.ClientConfig
}
type ClusterAddon struct {
Name string
Path string
}
func (c*ClusterAddons) AddonsPath() (vfs.Path, error) {
// TODO: Close NodeSSH
// TODO: What if endpoint is a load balancer? Query cloud and try to find actual hosts?
// TODO: What if multiple masters?
hostname := c.APIEndpoint
hostname = strings.TrimPrefix(hostname, "http://")
hostname = strings.TrimPrefix(hostname, "https://")
master := &NodeSSH{
Hostname: hostname,
}
master.SSHConfig = c.SSHConfig
root, err := master.Root()
if err != nil {
return nil, err
}
manifests := root.Join("etc", "kubernetes", "addons")
return manifests, nil
}
func (c *ClusterAddons) ListAddons() (map[string]*ClusterAddon, error) {
addonsPath, err := c.AddonsPath()
if err != nil {
return nil, err
}
files, err := addonsPath.ReadDir()
if err != nil {
return nil, fmt.Errorf("error reading addons: %v", err)
}
addons := make(map[string]*ClusterAddon)
for _, f := range files {
name := f.Base()
addon := &ClusterAddon{
Name: name,
Path: name,
}
addons[addon.Name] = addon
}
return addons, nil
}
func (c *ClusterAddons) CreateAddon(key string, files []vfs.Path) (error) {
addonsPath, err := c.AddonsPath()
if err != nil {
return err
}
addonPath := addonsPath.Join(key)
existingFiles, err := addonPath.ReadDir()
if err == nil && len(existingFiles) != 0 {
return fmt.Errorf("addon %q already exists", key)
}
srcData := make(map[string][]byte)
for _, f := range files {
name := f.Base()
data, err := f.ReadFile()
if err != nil {
return fmt.Errorf("error reading file %s: %v", f, err)
}
srcData[name] = data
}
for k, data := range srcData {
destPath := addonPath.Join(k)
err := destPath.WriteFile(data)
if err != nil {
// TODO: Delete other files?
return fmt.Errorf("error writing file %s: %v", destPath, err)
}
}
return nil
}

63
upup/pkg/kutil/kubectl.go Normal file
View File

@ -0,0 +1,63 @@
package kutil
import (
"os"
"os/exec"
"github.com/golang/glog"
"strings"
"fmt"
)
type Kubectl struct {
KubectlPath string
}
func (k *Kubectl) GetCurrentContext() (string, error) {
s, err := k.execKubectl("config", "current-context")
if err != nil {
return "", err
}
s = strings.TrimSpace(s)
return s, nil
}
func (k *Kubectl) GetConfig(minify bool, output string) (string, error) {
// TODO: --context doesn't seem to work
args := []string{"config", "view"}
if minify {
args = append(args, "--minify")
}
if output != "" {
args = append(args, "--output", output)
}
s, err := k.execKubectl(args...)
if err != nil {
return "", err
}
s = strings.TrimSpace(s)
return s, nil
}
func (k *Kubectl) execKubectl(args ...string) (string, error) {
kubectlPath := k.KubectlPath
if kubectlPath == "" {
kubectlPath = "kubectl" // Assume in PATH
}
cmd := exec.Command(kubectlPath, args...)
env := os.Environ()
cmd.Env = env
human := cmd.Path + strings.Join(cmd.Args, " ")
glog.V(2).Infof("Running command: %s", human)
output, err := cmd.CombinedOutput()
if err != nil {
glog.Info("error running %s:", human)
glog.Info(string(output))
return string(output), fmt.Errorf("error running kubectl")
}
return string(output), err
}

103
upup/pkg/kutil/ssh.go Normal file
View File

@ -0,0 +1,103 @@
package kutil
import (
"golang.org/x/crypto/ssh"
"fmt"
"io/ioutil"
"k8s.io/kube-deploy/upup/pkg/fi/vfs"
)
type NodeSSH struct {
Hostname string
SSHConfig ssh.ClientConfig
sshClient *ssh.Client
}
func (m*NodeSSH) Root() (*vfs.SSHPath, error) {
client, err := m.GetSSHClient()
if err != nil {
return nil, err
}
sudo := true
return vfs.NewSSHPath(client, m.Hostname, "/", sudo), nil
}
func AddSSHIdentity(sshConfig *ssh.ClientConfig, p string) error {
a, err := parsePrivateKeyFile(p)
if err != nil {
return err
}
sshConfig.Auth = append(sshConfig.Auth, a)
return nil
}
func (m *NodeSSH) dial() (*ssh.Client, error) {
users := []string{"admin", "ubuntu"}
if m.SSHConfig.User != "" {
users = []string{m.SSHConfig.User}
}
var lastError error
for _, user := range users {
m.SSHConfig.User = user
sshClient, err := ssh.Dial("tcp", m.Hostname+":22", &m.SSHConfig)
if err == nil {
return sshClient, err
}
lastError = err
}
return nil, fmt.Errorf("error connecting to SSH on server %q: %v", m.Hostname, lastError)
}
func (m *NodeSSH) GetSSHClient() (*ssh.Client, error) {
if m.sshClient == nil {
sshClient, err := m.dial()
if err != nil {
return nil, err
}
m.sshClient = sshClient
}
return m.sshClient, nil
}
//func (m *NodeSSH) ReadFile(remotePath string) ([]byte, error) {
// b, err := m.exec("sudo cat " + remotePath)
// if err != nil {
// return nil, fmt.Errorf("error reading remote file %q: %v", remotePath, err)
// }
// return b, nil
//}
//func (m *NodeSSH) exec(cmd string) ([]byte, error) {
// client, err := m.GetSSHClient()
// if err != nil {
// return nil, err
// }
//
// session, err := client.NewSession()
// if err != nil {
// return nil, fmt.Errorf("error creating SSH session: %v", err)
// }
// defer session.Close()
//
// b, err := session.Output(cmd)
// if err != nil {
// return nil, fmt.Errorf("error executing command %q: %v", cmd, err)
// }
// return b, nil
//}
func parsePrivateKeyFile(p string) (ssh.AuthMethod, error) {
buffer, err := ioutil.ReadFile(p)
if err != nil {
return nil, fmt.Errorf("error reading SSH key file %q: %v", p, err)
}
key, err := ssh.ParsePrivateKey(buffer)
if err != nil {
return nil, fmt.Errorf("error parsing key file %q: %v", p, err)
}
return ssh.PublicKeys(key), nil
}