mirror of https://github.com/kubernetes/kops.git
Merge pull request #109 from justinsb/upup_addons
upup: experimental addon management
This commit is contained in:
commit
0d617c7ffb
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -23,3 +23,4 @@ import:
|
|||
- ssh
|
||||
- package: github.com/cloudfoundry-incubator/candiedyaml
|
||||
- package: github.com/spf13/cobra
|
||||
- package: github.com/pkg/sftp
|
||||
|
|
|
|||
|
|
@ -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
|
||||
//}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue