From 9cebcf6a260bd62fafbecbfe9f1e56bd8d1a83c1 Mon Sep 17 00:00:00 2001 From: Emeline Gaulard Date: Fri, 8 Jun 2018 16:39:34 +0200 Subject: [PATCH] Deploy nerd dependencies on private clusters when necessary (#418) * Get catalog files and deserialize them * Check if cluster is nerd compliant * don't write certificate for private clusters * clean comments and add labels again --- ...{cluster_set_default.go => cluster_use.go} | 57 +++++-- cmd/flex/main.go | 6 + cmd/opts.go | 34 ++-- glide.lock | 123 +++++++-------- glide.yaml | 12 +- main.go | 30 ++-- make.sh | 1 + pkg/kubevisor/types.go | 77 ++++++++++ pkg/kubevisor/visor.go | 142 +++++++++++------ pkg/populator/generic.go | 16 +- svc/kube.go | 2 +- svc/kube_nerd_compliant.go | 145 ++++++++++++++++++ svc/svc.go | 15 +- 13 files changed, 482 insertions(+), 178 deletions(-) rename cmd/{cluster_set_default.go => cluster_use.go} (73%) create mode 100644 pkg/kubevisor/types.go create mode 100644 svc/kube_nerd_compliant.go diff --git a/cmd/cluster_set_default.go b/cmd/cluster_use.go similarity index 73% rename from cmd/cluster_set_default.go rename to cmd/cluster_use.go index 147d2ddbb..33e30c5fb 100644 --- a/cmd/cluster_set_default.go +++ b/cmd/cluster_use.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "fmt" "net/url" "os" @@ -14,19 +15,25 @@ import ( "github.com/nerdalize/nerd/nerd/oauth" "github.com/nerdalize/nerd/pkg/kubeconfig" "github.com/nerdalize/nerd/pkg/populator" + "github.com/nerdalize/nerd/svc" "github.com/pkg/errors" ) -//ClusterSetDefault command -type ClusterSetDefault struct { +const ( + //PublicCluster is a service type we get from authentication + PublicCluster = "public-kubernetes" +) + +//ClusterUse command +type ClusterUse struct { Namespace string `long:"namespace" short:"n" description:"set a specific namespace as the default one"` *command } -//ClusterSetDefaultFactory creates the command -func ClusterSetDefaultFactory(ui cli.Ui) cli.CommandFactory { - cmd := &ClusterSetDefault{} +//ClusterUseFactory creates the command +func ClusterUseFactory(ui cli.Ui) cli.CommandFactory { + cmd := &ClusterUse{} cmd.command = createCommand(ui, cmd.Execute, cmd.Description, cmd.Usage, cmd, &ConfOpts{}, flags.None, "nerd cluster set-default") t, ok := cmd.advancedOpts.(*ConfOpts) if !ok { @@ -40,7 +47,7 @@ func ClusterSetDefaultFactory(ui cli.Ui) cli.CommandFactory { } //Execute runs the command -func (cmd *ClusterSetDefault) Execute(args []string) (err error) { +func (cmd *ClusterUse) Execute(args []string) (err error) { // TODO move this part to another func env := os.Getenv("NERD_ENV") if env == "staging" { @@ -55,6 +62,10 @@ func (cmd *ClusterSetDefault) Execute(args []string) (err error) { return errShowUsage(fmt.Sprintf(MessageNotEnoughArguments, 1, "")) } + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, cmd.globalOpts.KubeOpts.Timeout) + defer cancel() + authbase, err := url.Parse(cmd.config.Auth.APIEndpoint) if err != nil { return errors.Wrapf(err, "auth endpoint '%v' is not a valid URL", cmd.config.Auth.APIEndpoint) @@ -111,17 +122,31 @@ func (cmd *ClusterSetDefault) Execute(args []string) (err error) { return err } - // check if it's nerd ready (is app nlz-utils up ?) - // else apply helm chart - // - get catalogs (is it necessary ?) - // - get projects (can we have it from auth) - // - launch app - // how to get the right chart version ? + if cluster.ServiceType != PublicCluster { + deps, err := NewDeps(cmd.Logger(), cmd.globalOpts.KubeOpts) + if err != nil { + return renderConfigError(err, "failed to configure") + } + kube := svc.NewKube(deps) + + ok, nerdDependencies, err := kube.IsNerdCompliant(ctx) + if err != nil { + return err + } + if !ok { + cmd.out.Info("Cluster is not nerd compliant, installing dependencies...") + // TODO move this to a new command + err = kube.AddNerdDependencies(ctx, &svc.AddNerdDependenciesInput{Dependencies: nerdDependencies}) + if err != nil { + return err + } + } + } name := cluster.Name if name == "" { name = cluster.ShortName } - cmd.out.Infof("You are now using %s's config.", name) + cmd.out.Infof("You are now using '%s' config.", name) return nil } @@ -147,12 +172,12 @@ func lookByName(name string, clusters []*v1authpayload.GetClusterOutput) (*v1aut } // Description returns long-form help text -func (cmd *ClusterSetDefault) Description() string { return cmd.Synopsis() } +func (cmd *ClusterUse) Description() string { return cmd.Synopsis() } // Synopsis returns a one-line -func (cmd *ClusterSetDefault) Synopsis() string { +func (cmd *ClusterUse) Synopsis() string { return "Set a specific cluster as the current one to use." } // Usage shows usage -func (cmd *ClusterSetDefault) Usage() string { return "nerd cluster set-default NAME [OPTIONS]" } +func (cmd *ClusterUse) Usage() string { return "nerd cluster set-default NAME [OPTIONS]" } diff --git a/cmd/flex/main.go b/cmd/flex/main.go index db1cdae7f..8116f601b 100755 --- a/cmd/flex/main.go +++ b/cmd/flex/main.go @@ -24,6 +24,7 @@ import ( "github.com/joho/godotenv" "github.com/pkg/errors" "k8s.io/api/core/v1" + apiext "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" certutil "k8s.io/client-go/util/cert" @@ -822,3 +823,8 @@ func (deps *Deps) Namespace() string { func (deps *Deps) Crd() crd.Interface { return deps.crd } + +//APIExt implements the DI interface +func (deps *Deps) APIExt() apiext.Interface { + return nil +} diff --git a/cmd/opts.go b/cmd/opts.go index 1a4d5cfda..9655e0ca4 100755 --- a/cmd/opts.go +++ b/cmd/opts.go @@ -13,6 +13,7 @@ import ( "github.com/nerdalize/nerd/pkg/transfer/store" "github.com/nerdalize/nerd/svc" "github.com/pkg/errors" + apiext "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) @@ -59,17 +60,18 @@ func (opts TransferOpts) TransferManager(kube *svc.Kube) (mgr transfer.Manager, //KubeOpts can be used to create a Kubernetes service type KubeOpts struct { - KubeConfig string `long:"kubeconfig" description:"file at which Nerd will look for Kubernetes credentials" env:"KUBECONFIG" default-mask:"~/.kube/config" default:"~/.kube/config"` + KubeConfig string `long:"kubeconfig" description:"file at which Nerd will look for Kubernetes credentials" env:"KUBECONFIG" default-mask:"~/.kube/config"` Timeout time.Duration `long:"timeout" description:"duration for which Nerd will wait for Kubernetes" default-mask:"10s" default:"10s" required:"true"` } //Deps exposes dependencies type Deps struct { - val svc.Validator - kube kubernetes.Interface - crd crd.Interface - logs svc.Logger - ns string + val svc.Validator + kube kubernetes.Interface + crd crd.Interface + apiext apiext.Interface + logs svc.Logger + ns string } //NewDeps uses options to setup dependencies @@ -90,6 +92,11 @@ func NewDeps(logs svc.Logger, kopts KubeOpts) (*Deps, error) { logs: logs, } + d.apiext, err = apiext.NewForConfig(kcfg) + if err != nil { + return nil, errors.Wrap(err, "failed to create Kuberntes configuration") + } + d.crd, err = crd.NewForConfig(kcfg) if err != nil { return nil, errors.Wrap(err, "failed to create Kubernetes configuration") @@ -116,27 +123,32 @@ func NewDeps(logs svc.Logger, kopts KubeOpts) (*Deps, error) { return d, nil } -//Kube provides the kubernetes dependency +//Kube provides the kubernetes dependency. func (deps *Deps) Kube() kubernetes.Interface { return deps.kube } -//Validator provides the Validator dependency +//Validator provides the Validator dependency. func (deps *Deps) Validator() svc.Validator { return deps.val } -//Logger provides the Logger dependency +//Logger provides the Logger dependency. func (deps *Deps) Logger() svc.Logger { return deps.logs } -//Namespace provides the namespace dependency +//Namespace provides the namespace dependency. func (deps *Deps) Namespace() string { return deps.ns } -//Crd provides the custom resource definition API +//Crd provides the custom resource definition API. func (deps *Deps) Crd() crd.Interface { return deps.crd } + +//APIExt provides the extensions api to create a custom resource definition. +func (deps *Deps) APIExt() apiext.Interface { + return deps.apiext +} diff --git a/glide.lock b/glide.lock index f2e13dbb2..1493972bd 100644 --- a/glide.lock +++ b/glide.lock @@ -1,10 +1,10 @@ -hash: 61ecfcdeb4f758340f0aff2ca96866d1ecfb7369aa74a662469bea1982ece938 -updated: 2018-05-17T12:12:53.660630092+02:00 +hash: 8c8d549a39d10ac6e84cc0990e7c44ecf9aa0c727ede2811c5e4308a52eb3aea +updated: 2018-05-29T15:56:32.399854532+02:00 imports: - name: github.com/armon/go-radix version: 1fca145dffbcaa8fe914309b1ec0cfc67500fe61 - name: github.com/aws/aws-sdk-go - version: ee7b4b1162937cba700de23bd90acb742982e626 + version: 854f9d07a932452f1552955e127e50ba5ebb25cc subpackages: - aws - aws/awserr @@ -41,7 +41,7 @@ imports: - name: github.com/bgentry/speakeasy version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd - name: github.com/cheggaaa/pb - version: 1f7f140e48255d0e448ec06de47d4545b8ec1f85 + version: 2af8bbdea9e99e83b3ac400d8f6b6d1b8cbbf338 - name: github.com/davecgh/go-spew version: 782f4967f2dc4564575ca782fe2d04090b5faca8 subpackages: @@ -54,24 +54,10 @@ imports: version: 2268707a8f0843315e2004ee4f1d021dc08baedf - name: github.com/dustin/go-humanize version: 259d2a102b871d17f30e3cd9881a642961a1e486 -- name: github.com/emicklei/go-restful - version: ff4f55a206334ef123e4f79bbf348980da81ca46 - subpackages: - - log -- name: github.com/emicklei/go-restful-swagger12 - version: dcef7f55730566d41eae5db10e7d6981829720f6 - name: github.com/ghodss/yaml version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee -- name: github.com/go-openapi/jsonpointer - version: 46af16f9f7b149af66e5d1bd010e3574dc06de98 -- name: github.com/go-openapi/jsonreference - version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272 -- name: github.com/go-openapi/spec - version: 6aced65f8501fe1217321abf0749d354824ba2ff -- name: github.com/go-openapi/swag - version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72 - name: github.com/go-playground/locales - version: e4cbcb5d0652150d40ad0646651076b6bd2be4f6 + version: f63010822830b6fe52288ee52d5a1151088ce039 subpackages: - currency - name: github.com/go-playground/universal-translator @@ -86,7 +72,7 @@ imports: - name: github.com/golang/glog version: 44145f04b68cf362d9c4df2182967c2275eaefed - name: github.com/golang/protobuf - version: 4bd1920723d7b7c925de087aa32e2187708897f7 + version: 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9 subpackages: - proto - ptypes @@ -96,7 +82,7 @@ imports: - name: github.com/google/btree version: 7d79101e329e5a3adf994758c578dab82b90c017 - name: github.com/google/go-github - version: 08e68b58d6369e25c2375020353d0d642aa4b2b1 + version: 2ae5df7848328c214a48cec94c7d410cf8526527 subpackages: - github - name: github.com/google/go-querystring @@ -127,34 +113,28 @@ imports: - simplelru - name: github.com/hashicorp/logutils version: 0dc08b1671f34c4250ce212759ebd880f743d883 -- name: github.com/howeyc/gopass - version: bf9dde6d0d2c004a008c27aaee91170c786f6db8 - name: github.com/imdario/mergo version: 6633656539c1639d9d78127b7d47c622b5d7b6dc - name: github.com/jessevdk/go-flags version: 4e64e4a4e2552194cf594243e23aa9baf3b4297e - name: github.com/jmespath/go-jmespath - version: bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d + version: c2b33e8439af944379acbdd9c3a5fe0bc44bd8a5 - name: github.com/joho/godotenv version: a79fa1e548e2c689c241d10173efd51e5d689d5b - name: github.com/json-iterator/go - version: 36b14963da70d11297d313183d7e6388c8510e1e -- name: github.com/juju/ratelimit - version: 5b9ff866471762aa2ab2dced63c9fb6f53921342 -- name: github.com/mailru/easyjson - version: d5b7844b561a7bc640052f1b935f7b800330d7e0 - subpackages: - - buffer - - jlexer - - jwriter + version: 2ddf6d758266fcb080a4f9e054b9f292c85e6798 - name: github.com/mattn/go-isatty version: 6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c - name: github.com/mattn/go-runewidth - version: 97311d9f7767e3d6f422ea06661bc2c7a19e8a5d + version: ce7b0b5c7b45a81508558cd1dba6bb1e4ddb51bb - name: github.com/mitchellh/cli version: 65fcae5817c8600da98ada9d7edf26dd1a84837b - name: github.com/mitchellh/go-homedir version: b8bc1bf767474819792c23f32d8286a45736f1c6 +- name: github.com/modern-go/concurrent + version: bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94 +- name: github.com/modern-go/reflect2 + version: 05fbef0ca5da472bbf96c9322b84a53edc03c9fd - name: github.com/olekukonko/tablewriter version: febf2d34b54a69ce7530036c7503b1c9fbfdf0bb - name: github.com/peterbourgon/diskv @@ -162,15 +142,11 @@ imports: - name: github.com/pkg/errors version: 645ef00459ed84a119197bfb8d8205042c6df63d - name: github.com/posener/complete - version: 57878c9c0341001dd50e6ab0052c61cf9a79ad7f + version: e037c22b2fcfa85e74495388f03892ed194bba76 subpackages: - cmd - cmd/install - match -- name: github.com/PuerkitoBio/purell - version: 8a290539e2e8629dbc4e6bad948158f790ec31f4 -- name: github.com/PuerkitoBio/urlesc - version: 5bd2802263f21d8788851d5305584c82a5c75d7e - name: github.com/restic/chunker version: bb2ecf9a98e35a0b336ffc23fc515fb6e7961577 - name: github.com/satori/go.uuid @@ -185,20 +161,22 @@ imports: subpackages: - open - name: github.com/spf13/pflag - version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7 + version: 583c0c0531f06d5278b7d917446061adc344b5cd - name: golang.org/x/crypto - version: 6bd909f163c83732e0b5e22a27154b1a112c3ff9 + version: ab813273cd59e1333f7ae7bff5d027d4aadf528c subpackages: - ssh/terminal - name: golang.org/x/net version: 1c05540f6879653db88113bc4a2b70aec4bd491f subpackages: - context - - context/ctxhttp + - html + - html/atom - http2 - http2/hpack - idna - lex/httplex + - websocket - name: golang.org/x/oauth2 version: a6bd8cefa1811bd24b86f8902872e4e8225f74c4 subpackages: @@ -207,26 +185,23 @@ imports: - jws - jwt - name: golang.org/x/sys - version: 7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce + version: 95c6576299259db960f6c5b9b69ea52422860fce subpackages: - unix - windows - name: golang.org/x/text version: b19bf474d317b857955b12035d2c5acb57ce8b01 subpackages: - - cases - - internal - - internal/tag - - language - - runes - secure/bidirule - - secure/precis - transform - unicode/bidi - unicode/norm - - width +- name: golang.org/x/time + version: f51c12702a4d776e4c1fa9b0fabab841babae631 + subpackages: + - rate - name: google.golang.org/appengine - version: 5bee14b453b4c71be47ec1781b0fa61c2ea182db + version: b1f26356af11148e710935ed1ac8a7f5702c7612 subpackages: - internal - internal/base @@ -240,11 +215,13 @@ imports: - name: gopkg.in/inf.v0 version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 - name: gopkg.in/yaml.v2 - version: 53feefa2559fb8dfa8d81baad31be332c97d6c77 + version: 670d4cfef0544295bc27a114dbac37980d83185a - name: k8s.io/api - version: 389dfa299845bcf399c16af89987e8775718ea48 + version: 184e700b32b7f1b532b9fce8dd8c1f412d297c4b subpackages: - admissionregistration/v1alpha1 + - admissionregistration/v1beta1 + - apps/v1 - apps/v1beta1 - apps/v1beta2 - authentication/v1 @@ -258,31 +235,36 @@ imports: - batch/v2alpha1 - certificates/v1beta1 - core/v1 + - events/v1beta1 - extensions/v1beta1 - - imagepolicy/v1alpha1 - networking/v1 - policy/v1beta1 - rbac/v1 - rbac/v1alpha1 - rbac/v1beta1 - scheduling/v1alpha1 + - scheduling/v1beta1 - settings/v1alpha1 - storage/v1 + - storage/v1alpha1 - storage/v1beta1 +- name: k8s.io/apiextensions-apiserver + version: bd76ce7dd8e65e8c2197803a1e64869ca5905309 + subpackages: + - pkg/apis/apiextensions + - pkg/apis/apiextensions/v1beta1 - name: k8s.io/apimachinery - version: bc110fd540ab678abbf2bc71d9ce908eb9325ef6 + version: 084add7e300b3c88d77dded09a20d5063d632b5b subpackages: - - pkg/api/equality - pkg/api/errors - pkg/api/meta - pkg/api/resource - pkg/apis/meta/internalversion - pkg/apis/meta/v1 - pkg/apis/meta/v1/unstructured - - pkg/apis/meta/v1alpha1 + - pkg/apis/meta/v1beta1 - pkg/conversion - pkg/conversion/queryparams - - pkg/conversion/unstructured - pkg/fields - pkg/labels - pkg/runtime @@ -300,33 +282,28 @@ imports: - pkg/util/diff - pkg/util/errors - pkg/util/framer - - pkg/util/httpstream - - pkg/util/httpstream/spdy - pkg/util/intstr - pkg/util/json - - pkg/util/mergepatch - pkg/util/net - - pkg/util/remotecommand - pkg/util/runtime - pkg/util/sets - - pkg/util/strategicpatch - pkg/util/validation - pkg/util/validation/field - pkg/util/wait - pkg/util/yaml - pkg/version - pkg/watch - - third_party/forked/golang/json - - third_party/forked/golang/netutil - third_party/forked/golang/reflect - name: k8s.io/client-go - version: 3a46b5de730a74e064197a157c96c2d29724a677 + version: e1b1ad35184abf07819079215c7374ade5b576c9 subpackages: - discovery - discovery/fake - kubernetes - kubernetes/scheme - kubernetes/typed/admissionregistration/v1alpha1 + - kubernetes/typed/admissionregistration/v1beta1 + - kubernetes/typed/apps/v1 - kubernetes/typed/apps/v1beta1 - kubernetes/typed/apps/v1beta2 - kubernetes/typed/authentication/v1 @@ -340,6 +317,7 @@ imports: - kubernetes/typed/batch/v2alpha1 - kubernetes/typed/certificates/v1beta1 - kubernetes/typed/core/v1 + - kubernetes/typed/events/v1beta1 - kubernetes/typed/extensions/v1beta1 - kubernetes/typed/networking/v1 - kubernetes/typed/policy/v1beta1 @@ -347,10 +325,15 @@ imports: - kubernetes/typed/rbac/v1alpha1 - kubernetes/typed/rbac/v1beta1 - kubernetes/typed/scheduling/v1alpha1 + - kubernetes/typed/scheduling/v1beta1 - kubernetes/typed/settings/v1alpha1 - kubernetes/typed/storage/v1 + - kubernetes/typed/storage/v1alpha1 - kubernetes/typed/storage/v1beta1 + - pkg/apis/clientauthentication + - pkg/apis/clientauthentication/v1alpha1 - pkg/version + - plugin/pkg/client/auth/exec - plugin/pkg/client/auth/oidc - rest - rest/watch @@ -365,15 +348,13 @@ imports: - tools/pager - tools/reference - transport + - util/buffer - util/cert - util/flowcontrol - util/homedir - util/integer + - util/retry - util/workqueue - name: k8s.io/code-generator - version: 3c1fe2637f4efce271f1e6f50e039b2a0467c60c -- name: k8s.io/kube-openapi - version: 868f2f29720b192240e18284659231b440f9cda5 - subpackages: - - pkg/common + version: 2381612e86473457f7e1b8f7edf16cf1e191d859 testImports: [] diff --git a/glide.yaml b/glide.yaml index 770b4baea..3fa2b936e 100644 --- a/glide.yaml +++ b/glide.yaml @@ -39,7 +39,6 @@ import: - package: github.com/go-playground/validator version: ~v9.9.0 - package: k8s.io/client-go - version: kubernetes-1.8.8 - package: github.com/satori/go.uuid version: 36e9d2ebbde5e3f13ab2e25625fd453271d6522e subpackages: @@ -47,6 +46,14 @@ import: - tools/clientcmd - package: golang.org/x/sys - package: k8s.io/code-generator +- package: k8s.io/api + version: 184e700b32b7f1b532b9fce8dd8c1f412d297c4b + subpackages: + - apps/v1 +- package: k8s.io/apimachinery + version: 084add7e300b3c88d77dded09a20d5063d632b5b + subpackages: + - pkg/runtime - package: github.com/google/go-github subpackages: - github @@ -54,3 +61,6 @@ import: version: 1.2.0 - package: github.com/cheggaaa/pb version: ~v1.0.22 +- package: k8s.io/apiextensions-apiserver + subpackages: + - pkg/apis/apiextensions/v1beta1 diff --git a/main.go b/main.go index 20295a171..ab9483f71 100644 --- a/main.go +++ b/main.go @@ -39,21 +39,21 @@ func create() *cli.CLI { Args: args, HiddenCommands: []string{}, Commands: map[string]cli.CommandFactory{ - "version": cmd.VersionFactory(version, commit, ui), - "login": cmd.LoginFactory(ui), - "dataset": cmd.DatasetFactory(ui), - "dataset upload": cmd.DatasetUploadFactory(ui), - "dataset download": cmd.DatasetDownloadFactory(ui), - "dataset list": cmd.DatasetListFactory(ui), - "dataset delete": cmd.DatasetDeleteFactory(ui), - "job": cmd.JobFactory(ui), - "job run": cmd.JobRunFactory(ui), - "job list": cmd.JobListFactory(ui), - "job logs": cmd.JobLogsFactory(ui), - "job delete": cmd.JobDeleteFactory(ui), - "cluster": cmd.ClusterFactory(ui), - "cluster list": cmd.ClusterListFactory(ui), - "cluster set-default": cmd.ClusterSetDefaultFactory(ui), + "version": cmd.VersionFactory(version, commit, ui), + "login": cmd.LoginFactory(ui), + "dataset": cmd.DatasetFactory(ui), + "dataset upload": cmd.DatasetUploadFactory(ui), + "dataset download": cmd.DatasetDownloadFactory(ui), + "dataset list": cmd.DatasetListFactory(ui), + "dataset delete": cmd.DatasetDeleteFactory(ui), + "job": cmd.JobFactory(ui), + "job run": cmd.JobRunFactory(ui), + "job list": cmd.JobListFactory(ui), + "job logs": cmd.JobLogsFactory(ui), + "job delete": cmd.JobDeleteFactory(ui), + "cluster": cmd.ClusterFactory(ui), + "cluster list": cmd.ClusterListFactory(ui), + "cluster use": cmd.ClusterUseFactory(ui), }, } diff --git a/make.sh b/make.sh index 2ab9585ad..e9e8a16dc 100755 --- a/make.sh +++ b/make.sh @@ -52,6 +52,7 @@ function run_dev { #setup dev environment echo "--> updating dependencies" glide install + rm -r vendor/k8s.io/apiextensions-apiserver/vendor echo "--> checking crd generated code is valid" if ./crd/hack/verify-codegen.sh; then diff --git a/pkg/kubevisor/types.go b/pkg/kubevisor/types.go new file mode 100644 index 000000000..cfde47980 --- /dev/null +++ b/pkg/kubevisor/types.go @@ -0,0 +1,77 @@ +package kubevisor + +import ( + crd "github.com/nerdalize/nerd/crd/pkg/client/clientset/versioned" + apiext "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/client-go/kubernetes" +) + +//ResourceType is a type of Kubernetes resource +type ResourceType string + +var ( + //ResourceTypeJobs is used for job management + ResourceTypeJobs = ResourceType("jobs") + + //ResourceTypePods is used for pod inspection + ResourceTypePods = ResourceType("pods") + + //ResourceTypeDatasets is used for dataset management + ResourceTypeDatasets = ResourceType("datasets") + + //ResourceTypeEvents is the resource type for event fetching + ResourceTypeEvents = ResourceType("events") + + //ResourceTypeQuota can be used to retrieve quota information + ResourceTypeQuota = ResourceType("resourcequotas") + + //ResourceTypeSecrets can be used to get secret information + ResourceTypeSecrets = ResourceType("secrets") + + //ResourceTypeDeployments is used for deployment management + ResourceTypeDeployments = ResourceType("deployments") + + //ResourceTypeRoles is used for role management + ResourceTypeRoles = ResourceType("roles") + + //ResourceTypeRoleBindings is used for role bindings management + ResourceTypeRoleBindings = ResourceType("rolebindings") + + //ResourceTypeClusterRoles is used for cluster roles management + ResourceTypeClusterRoles = ResourceType("clusterroles") + + //ResourceTypeClusterRoleBindings is used for cluster role bindings management + ResourceTypeClusterRoleBindings = ResourceType("clusterrolebindings") + + //ResourceTypeDaemonsets is used for daemonset management + ResourceTypeDaemonsets = ResourceType("daemonsets") + + //ResourceTypeCustomResourceDefinition is used for crd management + ResourceTypeCustomResourceDefinition = ResourceType("customresourcedefinitions") +) + +//ManagedNames allows for Nerd to transparently manage resources based on names and there prefixes +type ManagedNames interface { + GetName() string + GetLabels() map[string]string + SetLabels(map[string]string) + SetName(name string) + SetGenerateName(name string) +} + +//ListTranformer must be implemented to allow Nerd to transparently manage resource names +type ListTranformer interface { + Transform(fn func(in ManagedNames) (out ManagedNames)) + Len() int +} + +//Visor provides access to Kubernetes resources while transparently filtering, naming and labeling +//resources that are managed by the CLI. +type Visor struct { + prefix string + ns string + api kubernetes.Interface + crd crd.Interface + apiext apiext.Interface + logs Logger +} diff --git a/pkg/kubevisor/visor.go b/pkg/kubevisor/visor.go index fabe42989..afe283521 100644 --- a/pkg/kubevisor/visor.go +++ b/pkg/kubevisor/visor.go @@ -11,6 +11,7 @@ import ( crdscheme "github.com/nerdalize/nerd/crd/pkg/client/clientset/versioned/scheme" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" + apiext "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" kuberr "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -31,65 +32,17 @@ type Logger interface { Debugf(format string, args ...interface{}) } -//ResourceType is a type of Kubernetes resource -type ResourceType string - -var ( - //ResourceTypeJobs is used for job management - ResourceTypeJobs = ResourceType("jobs") - - //ResourceTypePods is used for pod inspection - ResourceTypePods = ResourceType("pods") - - //ResourceTypeDatasets is used for dataset management - ResourceTypeDatasets = ResourceType("datasets") - - //ResourceTypeEvents is the resource type for event fetching - ResourceTypeEvents = ResourceType("events") - - //ResourceTypeQuota can be used to retrieve quota information - ResourceTypeQuota = ResourceType("resourcequotas") - - //ResourceTypeSecrets can be used to get secret information - ResourceTypeSecrets = ResourceType("secrets") -) - -//ManagedNames allows for Nerd to transparently manage resources based on names and there prefixes -type ManagedNames interface { - GetName() string - GetLabels() map[string]string - SetLabels(map[string]string) - SetName(name string) - SetGenerateName(name string) -} - -//ListTranformer must be implemented to allow Nerd to transparently manage resource names -type ListTranformer interface { - Transform(fn func(in ManagedNames) (out ManagedNames)) - Len() int -} - -//Visor provides access to Kubernetes resources while transparently filtering, naming and labeling -//resources that are managed by the CLI. -type Visor struct { - prefix string - ns string - api kubernetes.Interface - crd crd.Interface - logs Logger -} - var ( //used during deletion but requires an address to create a pointer for deletePropagationForeground = metav1.DeletePropagationForeground ) //NewVisor will setup a Kubernetes visor -func NewVisor(ns, prefix string, api kubernetes.Interface, crd crd.Interface, logs Logger) *Visor { +func NewVisor(ns, prefix string, api kubernetes.Interface, crd crd.Interface, apiext apiext.Interface, logs Logger) *Visor { if prefix == "" { prefix = DefaultPrefix } - return &Visor{prefix, ns, api, crd, logs} + return &Visor{prefix, ns, api, crd, apiext, logs} } func (k *Visor) hasPrefix(n string) bool { @@ -119,6 +72,8 @@ func (k *Visor) GetResource(ctx context.Context, t ResourceType, v ManagedNames, c = k.api.CoreV1().RESTClient() case ResourceTypeDatasets: c = k.crd.NerdalizeV1().RESTClient() + case ResourceTypeDaemonsets, ResourceTypeDeployments: + c = k.api.AppsV1().RESTClient() default: return errors.Errorf("unknown Kubernetes resource type provided: '%s'", t) } @@ -143,6 +98,40 @@ func (k *Visor) GetResource(ctx context.Context, t ResourceType, v ManagedNames, return nil } +//GetClusterResource will use the kube RESTClient to describe a resource by its name. +func (k *Visor) GetClusterResource(ctx context.Context, t ResourceType, v ManagedNames, name string) (err error) { + vv, ok := v.(runtime.Object) + if !ok { + return errors.Errorf("provided value was not castable to runtime.Object") + } + + var c rest.Interface + switch t { + case ResourceTypeCustomResourceDefinition: + c = k.apiext.ApiextensionsV1beta1().RESTClient() + case ResourceTypeRoles, ResourceTypeRoleBindings, ResourceTypeClusterRoles, ResourceTypeClusterRoleBindings: + c = k.api.RbacV1().RESTClient() + default: + return errors.Errorf("unknown Kubernetes resource type provided: '%s'", t) + } + + k.logs.Debugf("getting %s '%s' in namespace '%s': %s", t, name, k.ns, ctx) + err = c.Get(). + Name(name). + Resource(string(t)). + Body(vv). + Context(ctx). + Do(). + Into(vv) + + if err != nil { + return k.tagError(err) + } + + v.SetName(k.removePrefix(v.GetName())) //normalize back to unprefixed resource name + return nil +} + //DeleteResource will use the kube RESTClient to delete a resource by its name. func (k *Visor) DeleteResource(ctx context.Context, t ResourceType, name string) (err error) { var c rest.Interface @@ -218,6 +207,12 @@ func (k *Visor) CreateResource(ctx context.Context, t ResourceType, v ManagedNam case ResourceTypeJobs: c = k.api.BatchV1().RESTClient() genfix = "j-" + case ResourceTypePods: + c = k.api.CoreV1().RESTClient() + genfix = "p-" + case ResourceTypeDeployments, ResourceTypeDaemonsets: + c = k.api.AppsV1().RESTClient() + genfix = "d-" case ResourceTypeSecrets: c = k.api.CoreV1().RESTClient() genfix = "s-" @@ -228,7 +223,7 @@ func (k *Visor) CreateResource(ctx context.Context, t ResourceType, v ManagedNam return errors.Errorf("unknown Kubernetes resource type provided for creation: '%s'", t) } - if name != "" { + if name != "" && genfix != "" { v.SetName(k.applyPrefix(name)) } else { v.SetGenerateName(k.prefix + genfix) @@ -259,6 +254,51 @@ func (k *Visor) CreateResource(ctx context.Context, t ResourceType, v ManagedNam return nil } +//CreateClusterResource will use the kube RESTClient to create a cluster resource while using the context, adding the +//Nerd prefix and handling errors specific to our domain. +func (k *Visor) CreateClusterResource(ctx context.Context, t ResourceType, v ManagedNames, name string) (err error) { + vv, ok := v.(runtime.Object) + if !ok { + return errors.Errorf("provided value was not castable to runtime.Object") + } + + var c rest.Interface + switch t { + case ResourceTypeCustomResourceDefinition: + c = k.apiext.ApiextensionsV1beta1().RESTClient() + case ResourceTypeRoles, ResourceTypeRoleBindings, ResourceTypeClusterRoles, ResourceTypeClusterRoleBindings: + c = k.api.RbacV1().RESTClient() + default: + return errors.Errorf("unknown Kubernetes resource type provided for creation: '%s'", t) + } + + if name == "" { + v.SetGenerateName(k.prefix) + } + + labels := v.GetLabels() + if labels == nil { + labels = map[string]string{} + } + labels["nerd-app"] = "cli" + v.SetLabels(labels) + + k.logs.Debugf("creating %s '%s' with labels '%v': %s", t, v.GetName(), labels, ctx) + err = c.Post(). + Resource(string(t)). + Body(vv). + Context(ctx). + Do(). + Into(vv) + + if err != nil { + return k.tagError(err) + } + + v.SetName(k.removePrefix(v.GetName())) //normalize back to unprefixed resource name + return nil +} + //UpdateResource will use the kube RESTClient to update a resource while using the context, adding the //Nerd prefix and handling errors specific to our domain. func (k *Visor) UpdateResource(ctx context.Context, t ResourceType, v ManagedNames, name string) (err error) { diff --git a/pkg/populator/generic.go b/pkg/populator/generic.go index 207eedbff..20f8fe5b3 100644 --- a/pkg/populator/generic.go +++ b/pkg/populator/generic.go @@ -62,14 +62,16 @@ func (o *GenericPopulator) PopulateKubeConfig(namespace string) error { if o.cluster == nil { return errors.New("Cannot use an empty cluster") } - if o.cluster.CaCertificate == "" { - c.InsecureSkipTLSVerify = true - } else { - data, err := base64.StdEncoding.DecodeString(o.cluster.CaCertificate) - if err != nil { - return err + if o.cluster.ServiceType == "public-kubernetes" { + if o.cluster.CaCertificate == "" { + c.InsecureSkipTLSVerify = true + } else { + data, err := base64.StdEncoding.DecodeString(o.cluster.CaCertificate) + if err != nil { + return err + } + c.CertificateAuthorityData = data } - c.CertificateAuthorityData = data } c.Server = o.cluster.ServiceURL diff --git a/svc/kube.go b/svc/kube.go index bf30898a9..e75faf74b 100644 --- a/svc/kube.go +++ b/svc/kube.go @@ -16,7 +16,7 @@ type Kube struct { //NewKube will setup the Kubernetes service func NewKube(di DI) (k *Kube) { k = &Kube{ - visor: kubevisor.NewVisor(di.Namespace(), "", di.Kube(), di.Crd(), di.Logger()), + visor: kubevisor.NewVisor(di.Namespace(), "", di.Kube(), di.Crd(), di.APIExt(), di.Logger()), val: di.Validator(), logs: di.Logger(), } diff --git a/svc/kube_nerd_compliant.go b/svc/kube_nerd_compliant.go new file mode 100644 index 000000000..13a8b6583 --- /dev/null +++ b/svc/kube_nerd_compliant.go @@ -0,0 +1,145 @@ +package svc + +import ( + "context" + "fmt" + "io/ioutil" + "net/http" + + "github.com/nerdalize/nerd/pkg/kubevisor" + appsv1 "k8s.io/api/apps/v1" + rbacv1 "k8s.io/api/rbac/v1" + crdbeta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + extscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/scheme" + "k8s.io/client-go/kubernetes/scheme" +) + +var ( + files = map[string]string{ + "custom-dataset-controller": "https://raw.githubusercontent.com/nerdalize/catalog/master/templates/custom-dataset-controller.yaml", + "kube-system-cluster-role": "https://raw.githubusercontent.com/nerdalize/catalog/master/templates/kube-system-default.yaml", + "kube-system-clusterrolebinding": "https://raw.githubusercontent.com/nerdalize/catalog/master/templates/kube-system-default-clusterrolebinding.yaml", + "custom-dataset-definition": "https://raw.githubusercontent.com/nerdalize/catalog/master/templates/custom-dataset-definition.yaml", + "flexvolume-clusterrole": "https://raw.githubusercontent.com/nerdalize/catalog/master/templates/flexvolume-clusterrole.yaml", + "flexvolume-clusterrolebinding": "https://raw.githubusercontent.com/nerdalize/catalog/master/templates/flexvolume-clusterrolebinding.yml", + "flexvolume-daemonset": "https://raw.githubusercontent.com/nerdalize/catalog/master/templates/flexvolume-daemonset.yaml", + } +) + +// AddNerdDependenciesInput is used to configure the resource creation +type AddNerdDependenciesInput struct { + Dependencies []string + + // could be also used to specify a flexvolume version, + // and to propagate s3 credentials so that people can use by default their own private s3 bucket. +} + +// IsNerdCompliant checks if the nlz-utils are running on the current cluster +func (k *Kube) IsNerdCompliant(ctx context.Context) (ok bool, dependencies []string, err error) { + for resource, url := range files { + resp, err := http.Get(url) + if err != nil { + return false, nil, err + } + defer resp.Body.Close() + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return false, nil, err + } + + var obj interface{} + if resource == "custom-dataset-definition" { + decode := extscheme.Codecs.UniversalDeserializer().Decode + obj, _, err = decode(data, nil, nil) + } else { + decode := scheme.Codecs.UniversalDeserializer().Decode + obj, _, err = decode(data, nil, nil) + + } + if err != nil { + return false, []string{}, fmt.Errorf("Error while decoding YAML object. Err was: %s", err) + } + + switch o := obj.(type) { + case *appsv1.Deployment: + err = k.visor.GetResource(ctx, kubevisor.ResourceTypeDeployments, &appsv1.Deployment{}, o.Name) + case *appsv1.DaemonSet: + o.Namespace = "default" + err = k.visor.GetResource(ctx, kubevisor.ResourceTypeDaemonsets, &appsv1.DaemonSet{}, o.Name) + case *rbacv1.ClusterRole: + err = k.visor.GetClusterResource(ctx, kubevisor.ResourceTypeClusterRoles, &rbacv1.ClusterRole{}, o.Name) + case *rbacv1.ClusterRoleBinding: + err = k.visor.GetClusterResource(ctx, kubevisor.ResourceTypeClusterRoleBindings, &rbacv1.ClusterRoleBinding{}, o.Name) + case *crdbeta1.CustomResourceDefinition: + err = k.visor.GetClusterResource(ctx, kubevisor.ResourceTypeCustomResourceDefinition, &crdbeta1.CustomResourceDefinition{}, o.Name) + default: + //o is unknown for us + } + if err != nil { + if kubevisor.IsNotExistsErr(err) { + dependencies = append(dependencies, resource) + } else { + return false, []string{}, err + } + } + } + if len(dependencies) == 0 { + return true, dependencies, nil + } + return false, dependencies, nil +} + +// AddNerdDependencies will deploy necessary daemonsets, controllers and roles so that a private cluster can be used by the cli +func (k *Kube) AddNerdDependencies(ctx context.Context, in *AddNerdDependenciesInput) (err error) { + for _, dependency := range in.Dependencies { + // Get the data + resp, err := http.Get(files[dependency]) + if err != nil { + return err + } + defer resp.Body.Close() + // data to yaml + // pass config to kubernetes + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + var obj interface{} + + if dependency == "custom-dataset-definition" { + decode := extscheme.Codecs.UniversalDeserializer().Decode + obj, _, err = decode(data, nil, nil) + } else { + decode := scheme.Codecs.UniversalDeserializer().Decode + obj, _, err = decode(data, nil, nil) + + } + if err != nil { + return fmt.Errorf("Error while decoding YAML object. Err was: %s", err) + } + // now use switch over the type of the object + // and match each type-case + + switch o := obj.(type) { + case *appsv1.Deployment: + err = k.visor.CreateResource(ctx, kubevisor.ResourceTypeDeployments, o, o.Name) + case *appsv1.DaemonSet: + err = k.visor.CreateResource(ctx, kubevisor.ResourceTypeDaemonsets, o, o.Name) + case *rbacv1.ClusterRole: + err = k.visor.CreateClusterResource(ctx, kubevisor.ResourceTypeClusterRoles, o, o.Name) + case *rbacv1.ClusterRoleBinding: + err = k.visor.CreateClusterResource(ctx, kubevisor.ResourceTypeClusterRoleBindings, o, o.Name) + case *crdbeta1.CustomResourceDefinition: + err = k.visor.CreateClusterResource(ctx, kubevisor.ResourceTypeCustomResourceDefinition, o, o.Name) + default: + //o is unknown for us + } + if err != nil { + return err + } + } + return err +} diff --git a/svc/svc.go b/svc/svc.go index daa035d04..c8440501e 100755 --- a/svc/svc.go +++ b/svc/svc.go @@ -14,6 +14,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "k8s.io/api/core/v1" + apiext "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" @@ -34,6 +35,7 @@ type Logger interface { type DI interface { Kube() kubernetes.Interface Crd() crd.Interface + APIExt() apiext.Interface Validator() Validator Logger() Logger Namespace() string @@ -112,11 +114,12 @@ func TempDI(name string) (di DI, clean func(), err error) { } type tmpDI struct { - kube kubernetes.Interface - crd crd.Interface - val Validator - logs Logger - ns string + kube kubernetes.Interface + crd crd.Interface + apiExt apiext.Interface + val Validator + logs Logger + ns string } func (di *tmpDI) Kube() kubernetes.Interface { return di.kube } @@ -128,3 +131,5 @@ func (di *tmpDI) Logger() Logger { return di.logs } func (di *tmpDI) Namespace() string { return di.ns } func (di *tmpDI) Crd() crd.Interface { return di.crd } + +func (di *tmpDI) APIExt() apiext.Interface { return di.apiExt }