diff --git a/bootstrap/.dockerignore b/bootstrap/.dockerignore index ba04d8fc5d8..f3b64113ec5 100644 --- a/bootstrap/.dockerignore +++ b/bootstrap/.dockerignore @@ -1,2 +1 @@ -vendor/** - +**/.git diff --git a/bootstrap/Makefile b/bootstrap/Makefile index 603abfb1e87..3460733c75c 100755 --- a/bootstrap/Makefile +++ b/bootstrap/Makefile @@ -13,14 +13,15 @@ # limitations under the License. # GCLOUD_PROJECT ?= kubeflow-images-public -GOLANG_VERSION ?= 1.11.2 +GOLANG_VERSION ?= 1.11.5 +GOPATH ?= $(HOME)/go # To build without the cache set the environment variable # export DOCKER_BUILD_OPTS=--no-cache -# set to GOLANG_GCFLAGS=-gcflags "all=-N -l" to debug -GOLANG_GCFLAGS ?= "" IMG ?= gcr.io/$(GCLOUD_PROJECT)/bootstrapper -TAG ?= $(eval TAG := $(shell date +v%Y%m%d)-$(shell git describe --tags --always --dirty)-$(shell git diff | shasum -a256 | cut -c -6))$(TAG) +TAG ?= $(shell git describe --tags --always --dirty) PORT ?= 2345 +export GO111MODULE=on +export GO=go all: build @@ -29,14 +30,26 @@ auth: # Run go fmt against code fmt: - go fmt ./cmd/... + $(GO) fmt ./pkg/... ./cmd/... # Run go vet against code vet: - go vet ./cmd/... + $(GO) vet ./pkg/... ./cmd/... -build-local: fmt vet - GO111MODULE=on go build -gcflags 'all=-N -l' -o bin/bootstrapper cmd/bootstrap/main.go +generate: + $(GO) generate ./pkg/... ./cmd/... + + +build-bootstrap: generate fmt vet + $(GO) build -gcflags 'all=-N -l' -o bin/bootstrapper cmd/bootstrap/main.go + +build-kfctl: generate fmt vet + $(GO) build -i -gcflags 'all=-N -l' -o bin/kfctl cmd/kfctl/main.go + +build-foo-plugin: generate fmt vet + $(GO) build -i -gcflags 'all=-N -l' -o bin/fooapp.so -buildmode=plugin cmd/plugins/fooapp.go + +build-local: build-bootstrap build-kfctl # To edit which registries to add to bootstrapper, edit config (eg. config/default.yaml) build: build-local @@ -58,21 +71,35 @@ push-latest: push gcloud container images add-tag --quiet $(IMG):$(TAG) $(IMG):latest --verbosity=info echo created $(IMG):latest -run-local: - KUBERNETES_SERVICE_HOST=https://35.233.240.120 KUBERNETES_SERVICE_PORT=80 bin/bootstrapper --app-dir=$$HOME/apps --registries-config-file=$$PWD/local_registries.yaml --config=$$PWD/config/default.yaml --in-cluster=false +install: build-kfctl + @echo copying bin/kfctl to /usr/local/bin + @cp bin/kfctl /usr/local/bin run-local-docker: docker run -d -it --name bootstrapper \ - --mount type=bind,source=$$HOME/kf_app,target=/home/kubeflow \ + --mount type=bind,source=${HOME}/kf_app,target=/home/kubeflow \ --entrypoint /bin/bash $(IMG):$(TAG) -debug: push cleanup - @echo debugging $(IMG):$(TAG) using port $(PORT) - @./debug.sh $(IMG) $(TAG) $(PORT) +# init ~/myapp --platform none +test-known-platforms-init: install build-foo-plugin + @rm -rf $(HOME)/ks-app && \ + kfctl init $(HOME)/ks-app -V --platform none && \ + rm -rf $(HOME)/minikube-app && \ + kfctl init $(HOME)/minikube-app -V --platform minikube && \ + rm -rf $(HOME)/foo-app && \ + PLUGINS_ENVIRONMENT=$(GOPATH)/src/github.com/kubeflow/kubeflow/bootstrap/bin kfctl init $(HOME)/foo-app -V --platform foo && \ + echo SUCCESS -debug-latest: debug - @echo debugging $(IMG):latest using port $(PORT) - @./debug.sh $(IMG) latest $(PORT) +# generate all --email john@foo.com --ipName 35.233.240.120 +test-known-platforms-generate: test-known-platforms-init + @cd ~/ks-app && \ + kfctl generate all -V && \ + cd ~/minikube-app && \ + kfctl generate all -V --mount-local && \ + cd ~/foo-app && \ + PLUGINS_ENVIRONMENT=$(GOPATH)/src/github.com/kubeflow/kubeflow/bootstrap/bin kfctl generate all -V && \ + echo SUCCESS -cleanup: - @./cleanup.sh kubeflow-admin +test-foo-plugin: + rm -rf $(HOME)/foo-app + PLUGINS_ENVIRONMENT=$(GOPATH)/src/github.com/kubeflow/kubeflow/bootstrap/bin kfctl init $(HOME)/foo-app --platform foo diff --git a/bootstrap/cmd/bootstrap/app/gcpUtils.go b/bootstrap/cmd/bootstrap/app/gcpUtils.go index 52438dab404..46b58681113 100644 --- a/bootstrap/cmd/bootstrap/app/gcpUtils.go +++ b/bootstrap/cmd/bootstrap/app/gcpUtils.go @@ -69,7 +69,7 @@ func (s *ksServer) InsertDeployment(ctx context.Context, req CreateRequest, dmSp dmconf.Resources[0].Name = req.Name dmconf.Resources[0].Properties["zone"] = req.Zone dmconf.Resources[0].Properties["ipName"] = req.IpName - dmconf.Resources[0].Properties["createPipelinePersistentStorage"]=req.StorageOption.CreatePipelinePersistentStorage + dmconf.Resources[0].Properties["createPipelinePersistentStorage"] = req.StorageOption.CreatePipelinePersistentStorage // https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1/projects.zones.clusters if s.gkeVersionOverride != "" { dmconf.Resources[0].Properties["cluster-version"] = s.gkeVersionOverride diff --git a/bootstrap/cmd/bootstrap/app/ksServer.go b/bootstrap/cmd/bootstrap/app/ksServer.go index 6fb19d29e64..db3feba6fdd 100644 --- a/bootstrap/cmd/bootstrap/app/ksServer.go +++ b/bootstrap/cmd/bootstrap/app/ksServer.go @@ -24,6 +24,7 @@ import ( "github.com/ksonnet/ksonnet/pkg/actions" kApp "github.com/ksonnet/ksonnet/pkg/app" "github.com/ksonnet/ksonnet/pkg/client" + kstypes "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps/ksapp/v1alpha1" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" log "github.com/sirupsen/logrus" @@ -110,7 +111,7 @@ type ksServer struct { // This can be used to map the name of a registry to info about the registry. // This allows apps to specify a registry by name without having to know any // other information about the regisry. - knownRegistries map[string]RegistryConfig + knownRegistries map[string]*kstypes.RegistryConfig //gkeVersionOverride allows overriding the GKE version specified in DM config. If not set the value in DM config is used. // https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1/projects.zones.clusters @@ -149,7 +150,7 @@ func (m MultiError) ToError() error { } // NewServer constructs a ksServer. -func NewServer(appsDir string, registries []RegistryConfig, gkeVersionOverride string, installIstio bool) (*ksServer, error) { +func NewServer(appsDir string, registries []*kstypes.RegistryConfig, gkeVersionOverride string, installIstio bool) (*ksServer, error) { if appsDir == "" { return nil, fmt.Errorf("appsDir can't be empty") } @@ -157,7 +158,7 @@ func NewServer(appsDir string, registries []RegistryConfig, gkeVersionOverride s s := &ksServer{ appsDir: appsDir, projectLocks: make(map[string]*sync.Mutex), - knownRegistries: make(map[string]RegistryConfig), + knownRegistries: make(map[string]*kstypes.RegistryConfig), gkeVersionOverride: gkeVersionOverride, fs: afero.NewOsFs(), installIstio: installIstio, @@ -195,7 +196,7 @@ type CreateRequest struct { // Name for the app. Name string // AppConfig is the config for the app. - AppConfig AppConfig + AppConfig kstypes.AppConfig // Namespace for the app. Namespace string @@ -506,7 +507,7 @@ func (s *ksServer) CreateApp(ctx context.Context, request CreateRequest, dmDeplo // Add the registries to the app. for idx, registry := range request.AppConfig.Registries { - RegUri, err := s.getRegistryUri(®istry) + RegUri, err := s.getRegistryUri(registry) if err != nil { log.Errorf("There was a problem getRegistryUri for registry %v. Error: %v", registry.Name, err) return err @@ -589,17 +590,17 @@ func (s *ksServer) CreateApp(ctx context.Context, request CreateRequest, dmDeplo // fetch remote registry to local disk, or use baked-in registry if version not specified in user request. // Then return registry's RegUri. -func (s *ksServer) getRegistryUri(registry *RegistryConfig) (string, error) { +func (s *ksServer) getRegistryUri(registry *kstypes.RegistryConfig) (string, error) { if registry.Name == "" || - registry.Path == "" || - registry.Repo == "" || - registry.Version == "" || - registry.Version == "default" { + registry.Path == "" || + registry.Repo == "" || + registry.Version == "" || + registry.Version == "default" { v, ok := s.knownRegistries[registry.Name] if !ok { return "", fmt.Errorf("Create request uses registry %v but some "+ - "required fields are not specified and this is not a known registry.", registry.Name) + "required fields are not specified and this is not a known registry.", registry.Name) } log.Infof("No remote registry provided for registry %v; setting URI to local %v.", registry.Name, v.RegUri) return v.RegUri, nil @@ -651,7 +652,7 @@ func runCmd(rawcmd string) error { } // appGenerate installs packages and creates components. -func (s *ksServer) appGenerate(kfApp kApp.App, appConfig *AppConfig) error { +func (s *ksServer) appGenerate(kfApp kApp.App, appConfig *kstypes.AppConfig) error { libs, err := kfApp.Libraries() if err != nil { @@ -676,7 +677,7 @@ func (s *ksServer) appGenerate(kfApp kApp.App, appConfig *AppConfig) error { _, err = s.fs.Stat(regFile) if err == nil { log.Infof("processing registry file %v ", regFile) - var ksRegistry KsRegistry + var ksRegistry kstypes.KsRegistry if LoadConfig(regFile, &ksRegistry) == nil { for pkgName, _ := range ksRegistry.Libraries { _, err = s.fs.Stat(path.Join(registry.RegUri, pkgName)) @@ -783,7 +784,7 @@ func (s *ksServer) createComponent(kfApp kApp.App, args []string) error { // autoConfigureApp attempts to automatically optimize the Kubeflow application // based on the cluster setup. -func (s *ksServer) autoConfigureApp(kfApp *kApp.App, appConfig *AppConfig, namespace string, config *rest.Config) error { +func (s *ksServer) autoConfigureApp(kfApp *kApp.App, appConfig *kstypes.AppConfig, namespace string, config *rest.Config) error { kubeClient, err := clientset.NewForConfig(rest.AddUserAgent(config, "kubeflow-bootstrapper")) if err != nil { @@ -1200,7 +1201,7 @@ func checkDeploymentFinished(svc KsService, req CreateRequest, deployName string } func finishDeployment(svc KsService, req CreateRequest, - clusterDmDeploy *deploymentmanager.Deployment, storageDmDeploy *deploymentmanager.Deployment) { + clusterDmDeploy *deploymentmanager.Deployment, storageDmDeploy *deploymentmanager.Deployment) { ctx := context.Background() ctx = context.WithValue(ctx, StartTime, time.Now()) @@ -1297,13 +1298,13 @@ func makeDeployEndpoint(svc KsService) endpoint.Endpoint { } req.AppConfig.Parameters = append( req.AppConfig.Parameters, - KsParameter{ + kstypes.KsParameter{ Component: "pipeline", Name: "mysqlPd", Value: req.Name + StorageDmSpec.DmNameSuffix + PipelineDbDiskSuffix}) req.AppConfig.Parameters = append( req.AppConfig.Parameters, - KsParameter{ + kstypes.KsParameter{ Component: "pipeline", Name: "nfsPd", Value: req.Name + StorageDmSpec.DmNameSuffix + PipelineNfsDiskSuffix}) diff --git a/bootstrap/cmd/bootstrap/app/options/options.go b/bootstrap/cmd/bootstrap/app/options/options.go index ec6a66d4833..c72ae47cdcc 100644 --- a/bootstrap/cmd/bootstrap/app/options/options.go +++ b/bootstrap/cmd/bootstrap/app/options/options.go @@ -27,6 +27,7 @@ type ServerOption struct { KeepAlive bool InstallIstio bool Port int + AppName string AppDir string Config string Email string diff --git a/bootstrap/cmd/bootstrap/app/server.go b/bootstrap/cmd/bootstrap/app/server.go index 20803a27fa0..fce34c1cbce 100644 --- a/bootstrap/cmd/bootstrap/app/server.go +++ b/bootstrap/cmd/bootstrap/app/server.go @@ -26,6 +26,7 @@ import ( "github.com/ghodss/yaml" "github.com/kubeflow/kubeflow/bootstrap/cmd/bootstrap/app/options" + kstypes "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps/ksapp/v1alpha1" "github.com/kubeflow/kubeflow/bootstrap/version" log "github.com/sirupsen/logrus" "k8s.io/api/storage/v1" @@ -49,57 +50,11 @@ const GcloudPath = "gcloud" const RegistriesRoot = "/opt/registries" -type KsComponent struct { - Name string - Prototype string -} - -type KsPackage struct { - Name string - // Registry should be the name of the registry containing the package. - Registry string -} - -type KsParameter struct { - Component string - Name string - Value string -} - -// RegistryConfig is used for two purposes: -// 1. used during image build, to configure registries that should be baked into the bootstrapper docker image. -// (See: https://github.com/kubeflow/kubeflow/blob/master/bootstrap/image_registries.yaml) -// 2. used during app create rpc call, specifies a registry to be added to an app. -// required info for registry: Name, Repo, Version, Path -// Additionally if any of required fields is blank we will try to map with one of -// the registries baked into the Docker image using the name. -type RegistryConfig struct { - Name string - Repo string - Version string - Path string - RegUri string -} - -type AppConfig struct { - Registries []RegistryConfig - Packages []KsPackage - Components []KsComponent - Parameters []KsParameter -} - -// RegistriesConfigFile corresponds to a YAML file specifying information -// about known registries. -type RegistriesConfigFile struct { - // Registries provides information about known registries. - Registries []RegistryConfig -} - // AppConfigFile corresponds to a YAML file specifying information // about the app to create. type AppConfigFile struct { // App describes a ksonnet application. - App AppConfig + App kstypes.AppConfig } type LibrarySpec struct { @@ -107,14 +62,6 @@ type LibrarySpec struct { Path string } -// KsRegistry corresponds to ksonnet.io/registry -// which is the registry.yaml file found in every registry. -type KsRegistry struct { - ApiVersion string - Kind string - Libraries map[string]LibrarySpec -} - // Load yaml config func LoadConfig(path string, o interface{}) error { if path == "" { @@ -300,7 +247,7 @@ func Run(opt *options.ServerOption) error { } // Load information about the default registries. - var regConfig RegistriesConfigFile + var regConfig kstypes.RegistriesConfigFile if opt.RegistriesConfigFile != "" { log.Infof("Loading registry info in file %v", opt.RegistriesConfigFile) diff --git a/bootstrap/cmd/bootstrap/main.go b/bootstrap/cmd/bootstrap/main.go index b35261a7489..2248c976645 100644 --- a/bootstrap/cmd/bootstrap/main.go +++ b/bootstrap/cmd/bootstrap/main.go @@ -16,7 +16,6 @@ package main import ( "flag" - "github.com/onrik/logrus/filename" log "github.com/sirupsen/logrus" diff --git a/bootstrap/cmd/kfctl/.gitignore b/bootstrap/cmd/kfctl/.gitignore new file mode 100644 index 00000000000..d97ffc5159b --- /dev/null +++ b/bootstrap/cmd/kfctl/.gitignore @@ -0,0 +1,24 @@ + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Kubernetes Generated files - skip generated files, except for vendored files + +!vendor/**/zz_generated.* + +# editor and IDE paraphernalia +.idea +*.swp +*.swo +*~ diff --git a/bootstrap/cmd/kfctl/OWNERS b/bootstrap/cmd/kfctl/OWNERS new file mode 100644 index 00000000000..8b1f24f149e --- /dev/null +++ b/bootstrap/cmd/kfctl/OWNERS @@ -0,0 +1,6 @@ +approvers: + - jlewi + - kunmingg + - ashahba + - kkasravi +reviewers: diff --git a/bootstrap/cmd/kfctl/README.md b/bootstrap/cmd/kfctl/README.md new file mode 100644 index 00000000000..3813e014e49 --- /dev/null +++ b/bootstrap/cmd/kfctl/README.md @@ -0,0 +1,297 @@ +# kfctl golang client + +## Overview + +The new `kfctl` client replaces `kfctl.sh` and is implemented in golang. + +Note: Additional issues have been opened so this README.md will have additional updates. + +## Requirements + + - Create a common API for the UI (gcp-click-to-deploy) and `kfctl` (`KfApp`). + + - Separate different platform implementations of the [KfApp Interface](#kfapp-interface). + + - Allow new platforms to be added to kfctl without rebuilding or reshipping kfctl (see [Extending kfctl](#extending-kfctl) below). + + - Do not change existing `REST` entrypoints or the `KsService` interface in `ksServer.go` at this time. + + - Package `KfApp` interface and related types for ease of use by kfctl and (later) gcp-click-to-deploy. + +## API and Packaging + +New directories (`cmd/kfctl, pkg`): + +```sh +bootstrap/cmd/kfctl +bootstrap/cmd/kfctl/cmd +bootstrap/pkg +bootstrap/pkg/apis +bootstrap/pkg/apis/apps +bootstrap/pkg/apis/apps/ksapp/v1alpha1 +bootstrap/pkg/utils +bootstrap/pkg/client +bootstrap/pkg/client/ksapp +bootstrap/pkg/client/minikube +bootstrap/plugins +``` + +### KfApp Interface + +Definition: github/kubeflow/kubeflow/bootstrap/pkg/client/kfapi/typed/apps/group.go + +The `KfApp` golang Interface + +```golang +type ResourceEnum string +const ( + ALL ResourceEnum = "all" + K8S ResourceEnum = "k8s" + PLATFORM ResourceEnum = "platform" +) +// +// KfApp is used by commands under bootstrap/cmd/{bootstrap,kfctl}. KfApp provides a common +// API for different implementations like KsApp, GcpApp, MinikubeApp, etc. +// +type KfApp interface { + Apply(resources ResourceEnum, options map[string]interface{}) error + Delete(resources ResourceEnum, options map[string]interface{}) error + Generate(resources ResourceEnum, options map[string]interface{}) error + Init(options map[string]interface{}) error +} +``` + +kfctl includes platforms that implement the KfApp interface. (gcp will be added in the next phase) + +- platform: **none** + - bootstrap/pkg/client/ksapp/ksapp.go +- platform: **minikube** + - bootstrap/pkg/client/minikube/minikube.go +- platform: **docker-for-desktop** (in progress) + - bootstrap/pkg/client/dockerfordesktop/dockerfordesktop.go +- platform: **ack** (in progress) + - bootstrap/pkg/client/ack/ack.go + +## Usage + +```man +kubeflow client tool + +Usage: + kfctl [command] + +Available Commands: + apply Deploy a generated kubeflow application. + delete Delete a kubeflow application. + generate Generate a kubeflow application where resources is one of 'platform | k8s | all'. + help Help about any command + init Create a kubeflow application under <[path/]name> + version Prints the version of kfctl. + +Flags: + -h, --help help for kfctl + +Use "kfctl [command] --help" for more information about a command. +``` + +Typical use-case, non-platform specific. + +```sh +kfctl init ~/myapp +cd ~/myapp +kfctl generate +kfctl apply +``` + +## Subcommands + +### **init** (kubeflow/bootstrap/cmd/kfctl/cmd/init.go) + +``` +Create a kubeflow application under <[path/]name>. The <[path/]name> argument can either be a full path +or a name where the kubeflow application will be initialized in $PWD/name if is not a path or in the parent +directory is name is a path. + +Usage: + kfctl init <[path/]name> [flags] + +Flags: + -h, --help help for init + -n, --namespace string namespace where kubeflow will be deployed (default "kubeflow") + -p, --platform string one of 'gcp|minikube|docker-for-desktop|ack' (default "none") + --project string name of the gcp project if --platform gcp + -r, --repo string local github kubeflow repo (default "$GOPATH/src/github.com/kubeflow/kubeflow/kubeflow") + -V, --verbose verbose output default is false + -v, --version string desired version Kubeflow or latest tag if not provided by user (default "v0.4.1") +``` + +### **generate** (kubeflow/bootstrap/cmd/kfctl/cmd/generate.go) + +``` +Generate a kubeflow application where resources is one of 'platform | k8s | all'. + + platform: non kubernetes resources (eg --platform gcp) + k8s: kubernetes resources + all: both platform and k8s + +The default is 'all' for any selected platform. + +Usage: + kfctl generate [all(=default)|k8s|platform] [flags] + +Flags: + --email string email if '--platform gcp' + -h, --help help for generate + --ipName string ipName if '--platform gcp' + --mount-local mount-local if '--platform minikube || --platform docker-for-desktop' + -V, --verbose verbose output default is false +``` + +### **apply** (kubeflow/bootstrap/cmd/kfctl/cmd/apply.go) + +``` +Deploy a generated kubeflow application. + +Usage: + kfctl apply [all(=default)|k8s|platform] [flags] + +Flags: + -h, --help help for apply + -V, --verbose verbose output default is false +``` + +### **delete** (kubeflow/bootstrap/cmd/kfctl/cmd/delete.go) + +``` +Delete a kubeflow application. + +Usage: + kfctl delete [all(=default)|k8s|platform] [flags] + +Flags: + -h, --help help for delete + -V, --verbose verbose output default is false +``` + +--- + +## Extending kfctl + +`kfctl` can be extended to work with new platforms without requiring recompilation. +An example is under bootstrap/cmd/plugins/fooapp.go. A particular platform +provides a shared library (.so) under the env var `PLUGINS_ENVIRONMENT` +that kfctl would load and execute. The shared library needs to define + +``` +func GetKfApp(options map[string]interface{}) kftypes.KfApp +``` + +where the return type implements the [KfApp Interface](#kfapp-interface). + +In this sample, running + +``` +kfctl init ~/foo-app --platform foo +``` + +will result in kfctl loading $PLUGINS_ENVIRONMENT/fooapp.so and calling its methods that +implement the KfApp Interface. + +### Building the sample plugin + +``` +make build-foo-plugin +``` + +## Testing + +### Testing init for all platforms including the `foo` platform plugin + +``` +make test-known-platforms-init +``` + +### Testing generate for all platforms including the `foo` platform plugin + +``` +make test-known-platforms-generate +``` + +## Debugging + +In order to debug in goland, the plugin code must be disabled. +See https://github.com/golang/go/issues/23733. +This is expected to be resolved with golang 1.12. +You'll need to comment out a section in bootstrap/cmd/kfctl/cmd/root.go +so that the plugin package is not imported. +Change root.go (~#45) to look like below and goland debug should work. + +```golang + default: +/* + plugindir := os.Getenv("PLUGINS_ENVIRONMENT") + pluginpath := filepath.Join(plugindir, platform+".so") + p, err := plugin.Open(pluginpath) + if err != nil { + return nil, fmt.Errorf("could not load plugin %v for platform %v Error %v", pluginpath, platform, err) + } + symName := "Get" + strings.ToUpper(platform[0:1]) + platform[1:] + "App" + symbol, symbolErr := p.Lookup(symName) + if symbolErr != nil { + return nil, fmt.Errorf("could not find symbol %v for platform %v Error %v", symName, platform, symbolErr) + } + return symbol.(func(map[string]interface{}) kftypes.KfApp)(options), nil +*/ + return nil, fmt.Errorf("unknown platform %v", platform) + } +``` + +## KfApp Types used in app.yaml + +### ksonnet related types (originally under bootstrap/cmd/bootstrap, moved to pkg/apis/apps/ksapp/v1alpha1) + +```golang +type KsApp struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec KsAppSpec `json:"spec,omitempty"` + Status KsAppStatus `json:"status,omitempty"` +} + +type NameValue struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` +} + +// KsAppSpec defines the desired state of KsApp +type KsAppSpec struct { + Platform string `json:"platform,omitempty"` + Version string `json:"version,omitempty"` + Repo string `json:"repo,omitempty"` + Components []string `json:"components,omitempty"` + Packages []string `json:"packages,omitempty"` + Parameters map[string][]NameValue `json:"parameters,omitempty"` +} +``` + +#### app.yaml example for --platform none + +``` +apiVersion: ksapp.apps.kubeflow.org/v1alpha1 +kind: KsApp +metadata: + creationTimestamp: null + name: ks-app + namespace: kubeflow +spec: + platform: none + repo: /Users/kdkasrav/go/src/github.com/kubeflow/kubeflow/kubeflow + version: v0.4.1 +status: {} +``` + +## gcp-click-to-deploy (no changes) + +Ksonnet types have been moved to `github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps/ksapp/v1alpha1` + diff --git a/bootstrap/cmd/kfctl/cmd/apply.go b/bootstrap/cmd/kfctl/cmd/apply.go new file mode 100644 index 00000000000..f0e1b834785 --- /dev/null +++ b/bootstrap/cmd/kfctl/cmd/apply.go @@ -0,0 +1,72 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + kftypes "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var applyCfg = viper.New() + +// applyCmd represents the apply command +var applyCmd = &cobra.Command{ + Use: "apply [all(=default)|k8s|platform]", + Short: "Deploy a generated kubeflow application.", + Long: `Deploy a generated kubeflow application.`, + Run: func(cmd *cobra.Command, args []string) { + log.SetLevel(log.InfoLevel) + log.Info("deploying kubeflow application") + if applyCfg.GetBool(string(kftypes.VERBOSE)) == true { + log.SetLevel(log.InfoLevel) + } else { + log.SetLevel(log.WarnLevel) + } + resource, resourceErr := processResourceArg(args) + if resourceErr != nil { + log.Errorf("invalid resource: %v", resourceErr) + return + } + options := map[string]interface{}{} + kfApp, kfAppErr := loadKfApp(options) + if kfAppErr != nil { + log.Errorf("couldn't load KfApp: %v", kfAppErr) + return + } + applyErr := kfApp.Apply(resource, options) + if applyErr != nil { + log.Errorf("couldn't apply KfApp: %v", applyErr) + return + } + }, +} + +func init() { + rootCmd.AddCommand(applyCmd) + + applyCfg.SetConfigName("app") + applyCfg.SetConfigType("yaml") + + // verbose output + applyCmd.Flags().BoolP(string(kftypes.VERBOSE), "V", false, + string(kftypes.VERBOSE)+" output default is false") + bindErr := applyCfg.BindPFlag(string(kftypes.VERBOSE), applyCmd.Flags().Lookup(string(kftypes.VERBOSE))) + if bindErr != nil { + log.Errorf("couldn't set flag --%v: %v", string(kftypes.VERBOSE), bindErr) + return + } +} diff --git a/bootstrap/cmd/kfctl/cmd/delete.go b/bootstrap/cmd/kfctl/cmd/delete.go new file mode 100644 index 00000000000..69e789d5e5a --- /dev/null +++ b/bootstrap/cmd/kfctl/cmd/delete.go @@ -0,0 +1,72 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + kftypes "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var deleteCfg = viper.New() + +// deleteCmd represents the delete command +var deleteCmd = &cobra.Command{ + Use: "delete [all(=default)|k8s|platform]", + Short: "Delete a kubeflow application.", + Long: `Delete a kubeflow application.`, + Run: func(cmd *cobra.Command, args []string) { + log.SetLevel(log.InfoLevel) + log.Info("deleting kubeflow application") + if deleteCfg.GetBool(string(kftypes.VERBOSE)) == true { + log.SetLevel(log.InfoLevel) + } else { + log.SetLevel(log.WarnLevel) + } + resource, resourceErr := processResourceArg(args) + if resourceErr != nil { + log.Errorf("invalid resource: %v", resourceErr) + return + } + options := map[string]interface{}{} + kfApp, kfAppErr := loadKfApp(options) + if kfAppErr != nil { + log.Errorf("couldn't load KfApp: %v", kfAppErr) + return + } + deleteErr := kfApp.Delete(resource, options) + if deleteErr != nil { + log.Errorf("couldn't delete KfApp: %v", deleteErr) + return + } + }, +} + +func init() { + rootCmd.AddCommand(deleteCmd) + + deleteCfg.SetConfigName("app") + deleteCfg.SetConfigType("yaml") + + // verbose output + deleteCmd.Flags().BoolP(string(kftypes.VERBOSE), "V", false, + string(kftypes.VERBOSE)+" output default is false") + bindErr := deleteCfg.BindPFlag(string(kftypes.VERBOSE), deleteCmd.Flags().Lookup(string(kftypes.VERBOSE))) + if bindErr != nil { + log.Errorf("couldn't set flag --%v: %v", string(kftypes.VERBOSE), bindErr) + return + } +} diff --git a/bootstrap/cmd/kfctl/cmd/generate.go b/bootstrap/cmd/kfctl/cmd/generate.go new file mode 100644 index 00000000000..ccca46d4c69 --- /dev/null +++ b/bootstrap/cmd/kfctl/cmd/generate.go @@ -0,0 +1,112 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + kftypes "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var generateCfg = viper.New() + +// generateCmd represents the generate command +var generateCmd = &cobra.Command{ + Use: "generate [all(=default)|k8s|platform]", + Short: "Generate a kubeflow application where resources is one of 'platform | k8s | all'.", + Long: `Generate a kubeflow application where resources is one of 'platform | k8s | all'. + + platform: non kubernetes resources (eg --platform gcp) + k8s: kubernetes resources + all: both platform and k8s + +The default is 'all' for any selected platform.`, + Run: func(cmd *cobra.Command, args []string) { + log.SetLevel(log.InfoLevel) + log.Info("generating kubeflow application") + if generateCfg.GetBool(string(kftypes.VERBOSE)) == true { + log.SetLevel(log.InfoLevel) + } else { + log.SetLevel(log.WarnLevel) + } + resource, resourceErr := processResourceArg(args) + if resourceErr != nil { + log.Errorf("invalid resource: %v", resourceErr) + return + } + email := generateCfg.GetString(string(kftypes.EMAIL)) + ipName := generateCfg.GetString(string(kftypes.IPNAME)) + mountLocal := generateCfg.GetBool("mount-local") + options := map[string]interface{}{ + string(kftypes.EMAIL): email, + string(kftypes.IPNAME): ipName, + string(kftypes.MOUNT_LOCAL): mountLocal, + } + kfApp, kfAppErr := loadKfApp(options) + if kfAppErr != nil { + log.Errorf("couldn't load KfApp: %v", kfAppErr) + return + } + generateErr := kfApp.Generate(resource, options) + if generateErr != nil { + log.Errorf("couldn't generate KfApp: %v", generateErr) + return + } + }, +} + +func init() { + rootCmd.AddCommand(generateCmd) + + generateCfg.SetConfigName("app") + generateCfg.SetConfigType("yaml") + + // platform gcp + generateCmd.Flags().String(string(kftypes.EMAIL), "", + string(kftypes.EMAIL)+" if '--platform gcp'") + bindErr := generateCfg.BindPFlag(string(kftypes.EMAIL), generateCmd.Flags().Lookup(string(kftypes.EMAIL))) + if bindErr != nil { + log.Errorf("couldn't set flag --%v: %v", string(kftypes.EMAIL), bindErr) + return + } + + // platform gcp + generateCmd.Flags().String(string(kftypes.IPNAME), "", + string(kftypes.IPNAME)+" if '--platform gcp'") + bindErr = generateCfg.BindPFlag(string(kftypes.IPNAME), generateCmd.Flags().Lookup(string(kftypes.IPNAME))) + if bindErr != nil { + log.Errorf("couldn't set flag --%v: %v", string(kftypes.IPNAME), bindErr) + return + } + + // platforms minikube, docker-for-desktop + generateCmd.Flags().Bool(string(kftypes.MOUNT_LOCAL), false, + string(kftypes.MOUNT_LOCAL)+" if '--platform minikube || --platform docker-for-desktop'") + bindErr = generateCfg.BindPFlag(string(kftypes.MOUNT_LOCAL), generateCmd.Flags().Lookup(string(kftypes.MOUNT_LOCAL))) + if bindErr != nil { + log.Errorf("couldn't set flag --%v: %v", string(kftypes.MOUNT_LOCAL), bindErr) + return + } + + // verbose output + generateCmd.Flags().BoolP(string(kftypes.VERBOSE), "V", false, + string(kftypes.VERBOSE)+" output default is false") + bindErr = generateCfg.BindPFlag(string(kftypes.VERBOSE), generateCmd.Flags().Lookup(string(kftypes.VERBOSE))) + if bindErr != nil { + log.Errorf("couldn't set flag --%v: %v", string(kftypes.VERBOSE), bindErr) + return + } +} diff --git a/bootstrap/cmd/kfctl/cmd/init.go b/bootstrap/cmd/kfctl/cmd/init.go new file mode 100644 index 00000000000..b1a8a6d2480 --- /dev/null +++ b/bootstrap/cmd/kfctl/cmd/init.go @@ -0,0 +1,129 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "github.com/spf13/viper" + + kftypes "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var initCfg = viper.New() + +// initCmd represents the init command +var initCmd = &cobra.Command{ + Use: "init <[path/]name>", + Short: "Create a kubeflow application under <[path/]name>", + Long: `Create a kubeflow application under <[path/]name>. The <[path/]name> argument can either be a full path +or a name where the kubeflow application will be initialized in $PWD/name if is not a path or in the parent +directory is name is a path.`, + Run: func(cmd *cobra.Command, args []string) { + log.SetLevel(log.InfoLevel) + log.Info("initializing kubeflow application") + if initCfg.GetBool(string(kftypes.VERBOSE)) == true { + log.SetLevel(log.InfoLevel) + } else { + log.SetLevel(log.WarnLevel) + } + if len(args) == 0 { + log.Errorf("KsApp name is required") + return + } + appName := args[0] + platform := initCfg.GetString(string(kftypes.PLATFORM)) + namespace := initCfg.GetString(string(kftypes.NAMESPACE)) + version := initCfg.GetString(string(kftypes.VERSION)) + repo := initCfg.GetString(string(kftypes.REPO)) + project := initCfg.GetString(string(kftypes.PROJECT)) + + options := map[string]interface{}{ + string(kftypes.PLATFORM): platform, + string(kftypes.NAMESPACE): namespace, + string(kftypes.VERSION): version, + string(kftypes.APPNAME): appName, + string(kftypes.REPO): repo, + string(kftypes.PROJECT): project, + } + kfApp, kfAppErr := newKfApp(options) + if kfAppErr != nil { + log.Errorf("couldn't create KfApp: %v", kfAppErr) + return + } + initErr := kfApp.Init(options) + if initErr != nil { + log.Errorf("KfApp initialization failed: %v", initErr) + return + } + }, +} + +func init() { + rootCmd.AddCommand(initCmd) + + initCfg.SetConfigName("app") + initCfg.SetConfigType("yaml") + + initCmd.Flags().StringP(string(kftypes.PLATFORM), "p", kftypes.DefaultPlatform, + "one of 'gcp|minikube|docker-for-desktop|ack'") + bindErr := initCfg.BindPFlag(string(kftypes.PLATFORM), initCmd.Flags().Lookup(string(kftypes.PLATFORM))) + if bindErr != nil { + log.Errorf("couldn't set flag --%v: %v", string(kftypes.PLATFORM), bindErr) + return + } + + initCmd.Flags().StringP(string(kftypes.NAMESPACE), "n", kftypes.DefaultNamespace, + string(kftypes.NAMESPACE)+" where kubeflow will be deployed") + bindErr = initCfg.BindPFlag(string(kftypes.NAMESPACE), initCmd.Flags().Lookup(string(kftypes.NAMESPACE))) + if bindErr != nil { + log.Errorf("couldn't set flag --%v: %v", string(kftypes.NAMESPACE), bindErr) + return + } + + initCmd.Flags().StringP(string(kftypes.VERSION), "v", kftypes.DefaultVersion, + "desired "+string(kftypes.VERSION)+" Kubeflow or latest tag if not provided by user ") + bindErr = initCfg.BindPFlag(string(kftypes.VERSION), initCmd.Flags().Lookup(string(kftypes.VERSION))) + if bindErr != nil { + log.Errorf("couldn't set flag --%v: %v", string(kftypes.VERSION), bindErr) + return + } + + initCmd.Flags().StringP(string(kftypes.REPO), "r", kftypes.DefaultKfRepo, + "local github kubeflow "+string(kftypes.REPO)) + bindErr = initCfg.BindPFlag(string(kftypes.REPO), initCmd.Flags().Lookup(string(kftypes.REPO))) + if bindErr != nil { + log.Errorf("couldn't set flag --%v: %v", string(kftypes.REPO), bindErr) + return + } + + // platform gcp + initCmd.Flags().String(string(kftypes.PROJECT), "", + "name of the gcp "+string(kftypes.PROJECT)+" if --platform gcp") + bindErr = initCfg.BindPFlag(string(kftypes.PROJECT), initCmd.Flags().Lookup(string(kftypes.PROJECT))) + if bindErr != nil { + log.Errorf("couldn't set flag --%v: %v", string(kftypes.PROJECT), bindErr) + return + } + + // verbose output + initCmd.Flags().BoolP(string(kftypes.VERBOSE), "V", false, + string(kftypes.VERBOSE)+" output default is false") + bindErr = initCfg.BindPFlag(string(kftypes.VERBOSE), initCmd.Flags().Lookup(string(kftypes.VERBOSE))) + if bindErr != nil { + log.Errorf("couldn't set flag --%v: %v", string(kftypes.VERBOSE), bindErr) + return + } +} diff --git a/bootstrap/cmd/kfctl/cmd/root.go b/bootstrap/cmd/kfctl/cmd/root.go new file mode 100644 index 00000000000..7d2b023a961 --- /dev/null +++ b/bootstrap/cmd/kfctl/cmd/root.go @@ -0,0 +1,189 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + "github.com/ghodss/yaml" + "github.com/ksonnet/ksonnet/pkg/app" + kftypes "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps" + kstypes "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps/ksapp/v1alpha1" + "github.com/kubeflow/kubeflow/bootstrap/pkg/client/ksapp" + "github.com/kubeflow/kubeflow/bootstrap/pkg/client/minikube" + "github.com/mitchellh/go-homedir" + log "github.com/sirupsen/logrus" + "github.com/spf13/afero" + "github.com/spf13/cobra" + "io/ioutil" + "os" + "path" + "path/filepath" + "plugin" + "regexp" + "strings" +) + +func processResourceArg(args []string) (kftypes.ResourceEnum, error) { + if len(args) > 1 { + return kftypes.NONE, fmt.Errorf("unknown extra args %v", args[1:]) + } + resources := kftypes.ALL + if len(args) == 1 { + switch kftypes.ResourceEnum(args[0]) { + case kftypes.ALL: + case kftypes.K8S: + resources = kftypes.K8S + case kftypes.PLATFORM: + resources = kftypes.PLATFORM + default: + return kftypes.NONE, fmt.Errorf("unknown argument %v", args[0]) + } + } + return resources, nil +} + +func loadPlatform(options map[string]interface{}) (kftypes.KfApp, error) { + platform := options[string(kftypes.PLATFORM)].(string) + switch platform { + case "none": + _kfapp := ksapp.GetKfApp(options) + return _kfapp, nil + case "minikube": + _minikubeapp := minikube.GetKfApp(options) + return _minikubeapp, nil + default: + // To enable goland debugger: + // Comment out this section and comment in the line + // return nil, fmt.Errorf("unknown platform %v", platform + + plugindir := os.Getenv("PLUGINS_ENVIRONMENT") + pluginpath := filepath.Join(plugindir, platform+"app.so") + p, err := plugin.Open(pluginpath) + if err != nil { + return nil, fmt.Errorf("could not load plugin %v for platform %v Error %v", pluginpath, platform, err) + } + symName := "GetKfApp" + symbol, symbolErr := p.Lookup(symName) + if symbolErr != nil { + return nil, fmt.Errorf("could not find symbol %v for platform %v Error %v", symName, platform, symbolErr) + } + return symbol.(func(map[string]interface{}) kftypes.KfApp)(options), nil + + //return nil, fmt.Errorf("unknown platform %v", platform) + } +} + +func newKfApp(options map[string]interface{}) (kftypes.KfApp, error) { + //appName can be a path + appName := options[string(kftypes.APPNAME)].(string) + appDir := path.Dir(appName) + if appDir == "" { + cwd, err := os.Getwd() + if err != nil { + return nil, fmt.Errorf("could not get current directory %v", err) + } + appDir = cwd + } else { + if appDir == "~" { + home, homeErr := homedir.Dir() + if homeErr != nil { + return nil, fmt.Errorf("could not get home directory %v", homeErr) + } + expanded, expandedErr := homedir.Expand(home) + if expandedErr != nil { + return nil, fmt.Errorf("could not expand home directory %v", homeErr) + } + appName = path.Base(appName) + appDir = path.Join(expanded, appName) + } else { + appName = path.Base(appName) + appDir = path.Join(appDir, appName) + } + } + re := regexp.MustCompile(`[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) + validName := re.FindString(appName) + if strings.Compare(validName, appName) != 0 { + return nil, fmt.Errorf(`invalid name %v must consist of lower case alphanumeric characters, '-' or '.', +and must start and end with an alphanumeric character`, appName) + } + options[string(kftypes.APPNAME)] = appName + options[string(kftypes.APPDIR)] = appDir + platform := options[string(kftypes.PLATFORM)].(string) + pApp, pAppErr := loadPlatform(options) + if pAppErr != nil { + return nil, fmt.Errorf("unable to load platform %v Error: %v", platform, pAppErr) + } + return pApp, nil +} + +func loadKfApp(options map[string]interface{}) (kftypes.KfApp, error) { + appDir, err := os.Getwd() + if err != nil { + return nil, fmt.Errorf("could not get current directory %v", err) + } + appName := filepath.Base(appDir) + log.Infof("AppName %v AppDir %v", appName, appDir) + cfgfile := filepath.Join(appDir, kftypes.KfConfigFile) + log.Infof("reading from %v", cfgfile) + fs := afero.NewOsFs() + ksDir := path.Join(appDir, kstypes.KsName) + kApp, kAppErr := app.Load(fs, nil, ksDir) + if kAppErr != nil { + return nil, fmt.Errorf("there was a problem loading app %v. Error: %v", appName, kAppErr) + } + ksApp := &kstypes.KsApp{} + dat, datErr := ioutil.ReadFile(cfgfile) + if datErr != nil { + return nil, fmt.Errorf("couldn't read %v. Error: %v", cfgfile, datErr) + } + specErr := yaml.Unmarshal(dat, ksApp) + if specErr != nil { + return nil, fmt.Errorf("couldn't unmarshall KsApp. Error: %v", specErr) + } + options[string(kftypes.PLATFORM)] = ksApp.Spec.Platform + options[string(kftypes.APPNAME)] = appName + options[string(kftypes.APPDIR)] = appDir + options[string(kftypes.KAPP)] = kApp + options[string(kftypes.KSAPP)] = ksApp + pApp, pAppErr := loadPlatform(options) + if pAppErr != nil { + return nil, fmt.Errorf("unable to load platform %v Error: %v", ksApp.Spec.Platform, pAppErr) + } + return pApp, nil +} + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "kfctl", + Short: "A client tool to create kubeflow applications", + Long: `kubeflow client tool`, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) +} + +// initConfig creates a Viper config file and set's it's name and type +func initConfig() { +} diff --git a/bootstrap/cmd/kfctl/cmd/version.go b/bootstrap/cmd/kfctl/cmd/version.go new file mode 100644 index 00000000000..ebafc794805 --- /dev/null +++ b/bootstrap/cmd/kfctl/cmd/version.go @@ -0,0 +1,47 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// versionCmd represents the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Prints the version of kfctl.", + Long: `Prints the version of kfctl.`, + Run: versionfunc, +} + +func init() { + rootCmd.AddCommand(versionCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // versionCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +func versionfunc(cmd *cobra.Command, args []string) { + fmt.Println("v20181207-4e7f4ed-198-gaeea303e-dirty-03e65e") +} diff --git a/bootstrap/cmd/kfctl/config/app.yaml b/bootstrap/cmd/kfctl/config/app.yaml new file mode 100644 index 00000000000..2814ca14f54 --- /dev/null +++ b/bootstrap/cmd/kfctl/config/app.yaml @@ -0,0 +1,61 @@ +apiversion: apps.kubeflow.org/v1alpha1 +kind: KsApp +metadata: + name: kf-app + namespace: kubeflow +namespace: kubeflow +spec: + app: + packages: + - name: argo + registry: kubeflow + - name: pipeline + registry: kubeflow + - name: common + registry: kubeflow + - name: examples + registry: kubeflow + - name: jupyter + registry: kubeflow + - name: katib + registry: kubeflow + - name: mpi-job + registry: kubeflow + - name: pytorch-job + registry: kubeflow + - name: seldon + registry: kubeflow + - name: tf-serving + registry: kubeflow + - name: openvino + registry: kubeflow + - name: tensorboard + registry: kubeflow + - name: tf-training + registry: kubeflow + - name: metacontroller + registry: kubeflow + - name: profiles + registry: kubeflow + - name: application + registry: kubeflow + - name: modeldb + registry: kubeflow + parameters: + - component: spartakus + name: usageId + value: 43606238 + - component: spartakus + name: reportUsage + value: 1 + registries: + - RegUri: /Users/fred/go/src/github.com/kubeflow/kubeflow/kubeflow + name: kubeflow + path: kubeflow + repo: https://github.com/kubeflow/kubeflow.git + version: v0.4.1 + components: + - all + platform: none + repo: /Users/fred/go/src/github.com/kubeflow/kubeflow/kubeflow + version: v0.4.1 diff --git a/bootstrap/cmd/kfctl/main.go b/bootstrap/cmd/kfctl/main.go new file mode 100644 index 00000000000..606a4c8c474 --- /dev/null +++ b/bootstrap/cmd/kfctl/main.go @@ -0,0 +1,23 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "github.com/kubeflow/kubeflow/bootstrap/cmd/kfctl/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/bootstrap/cmd/plugins/fooapp.go b/bootstrap/cmd/plugins/fooapp.go new file mode 100644 index 00000000000..dafe0d79339 --- /dev/null +++ b/bootstrap/cmd/plugins/fooapp.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + kftypes "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps" + "github.com/kubeflow/kubeflow/bootstrap/pkg/client/ksapp" +) + +// FooApp implements KfApp Interface +// It includes the KsApp along with additional Foo types +type FooApp struct { + ksApp kftypes.KfApp + //TODO add additional types required for foo platform +} + +func GetKfApp(options map[string]interface{}) kftypes.KfApp { + _fooapp := &FooApp{ + ksApp: ksapp.GetKfApp(options), + } + return _fooapp +} + +func (fooApp *FooApp) writeConfigFile() error { + //TODO write files under AppDir + return nil +} + +func (fooApp *FooApp) Apply(resources kftypes.ResourceEnum, options map[string]interface{}) error { + ksApplyErr := fooApp.ksApp.Apply(resources, options) + if ksApplyErr != nil { + return fmt.Errorf("foo apply failed for ksapp: %v", ksApplyErr) + } + return nil +} + +func (fooApp *FooApp) Delete(resources kftypes.ResourceEnum, options map[string]interface{}) error { + ksDeleteErr := fooApp.ksApp.Delete(resources, options) + if ksDeleteErr != nil { + return fmt.Errorf("foo delete failed for ksapp: %v", ksDeleteErr) + } + return nil +} + +func (fooApp *FooApp) Generate(resources kftypes.ResourceEnum, options map[string]interface{}) error { + ksGenerateErr := fooApp.ksApp.Generate(resources, options) + if ksGenerateErr != nil { + return fmt.Errorf("foo generate failed for ksapp: %v", ksGenerateErr) + } + return nil +} + +func (fooApp *FooApp) Init(options map[string]interface{}) error { + ksInitErr := fooApp.ksApp.Init(options) + if ksInitErr != nil { + return fmt.Errorf("foo init failed for ksapp: %v", ksInitErr) + } + return nil +} diff --git a/bootstrap/go.mod b/bootstrap/go.mod index 6d5f5de4488..8b8e6ca7cff 100644 --- a/bootstrap/go.mod +++ b/bootstrap/go.mod @@ -1,126 +1,124 @@ module github.com/kubeflow/kubeflow/bootstrap require ( - cloud.google.com/go v0.32.0 - contrib.go.opencensus.io/exporter/stackdriver v0.0.0-20180910204836-9f333b48d382 - github.com/Azure/go-ansiterm v0.0.0-20170629204627-19f72df4d05d - github.com/Azure/go-autorest v9.9.0+incompatible - github.com/BurntSushi/toml v0.3.1 - github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da - github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd - github.com/Masterminds/semver v1.3.1 - github.com/Masterminds/sprig v2.16.0+incompatible - github.com/PuerkitoBio/purell v1.0.0 - github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2 - github.com/aokoli/goutils v1.0.1 - github.com/asaskevich/govalidator v0.0.0-20160715170612-593d64559f76 - github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 - github.com/blang/semver v3.5.0+incompatible - github.com/cenkalti/backoff v2.0.0+incompatible - github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb - github.com/davecgh/go-spew v1.1.1 - github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda - github.com/docker/distribution v0.0.0-20170726174610-edc3ab29cdff - github.com/docker/docker v0.0.0-20170731201938-4f3616fb1c11 - github.com/docker/go-connections v0.3.0 - github.com/docker/go-units v0.0.0-20170127094116-9e638d38cf69 - github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 - github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 - github.com/emicklei/go-restful-swagger12 v0.0.0-20170926063155-7524189396c6 - github.com/evanphx/json-patch v0.0.0-20170719203123-944e07253867 - github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d - github.com/fatih/camelcase v0.0.0-20160318181535-f6a740d52f96 - github.com/fatih/color v0.0.0-20180516100307-2d684516a886 + bitbucket.org/ww/goautoneg v0.0.0-20120707110453-75cd24fc2f2c // indirect + cloud.google.com/go v0.34.0 + contrib.go.opencensus.io/exporter/ocagent v0.4.2 // indirect + github.com/Azure/go-autorest v9.1.0+incompatible // indirect + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/GeertJohan/go.rice v0.0.0-20181229193832-0af3f3b09a0a // indirect + github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e // indirect + github.com/Masterminds/semver v1.4.2 // indirect + github.com/Masterminds/sprig v2.17.1+incompatible // indirect + github.com/NYTimes/gziphandler v1.0.1 // indirect + github.com/aokoli/goutils v1.1.0 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/cenkalti/backoff v2.1.1+incompatible + github.com/coreos/etcd v3.3.11+incompatible // indirect + github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142 // indirect + github.com/cyphar/filepath-securejoin v0.2.2 // indirect + github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/docker/distribution v2.7.0+incompatible // indirect + github.com/docker/docker v1.13.1 // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.3.3 // indirect + github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect + github.com/emicklei/go-restful v2.8.0+incompatible // indirect + github.com/emicklei/go-restful-swagger12 v0.0.0-20170926063155-7524189396c6 // indirect + github.com/evanphx/json-patch v4.1.0+incompatible // indirect + github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect + github.com/fatih/camelcase v1.0.0 // indirect + github.com/fatih/color v1.7.0 // indirect github.com/ghodss/yaml v1.0.0 - github.com/go-kit/kit v0.7.0 - github.com/go-logfmt/logfmt v0.3.0 - github.com/go-openapi/analysis v0.0.0-20160815203709-b44dc874b601 - github.com/go-openapi/errors v0.0.0-20160704190347-d24ebc2075ba - github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1 - github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9 - github.com/go-openapi/loads v0.0.0-20170520182102-a80dea3052f0 - github.com/go-openapi/runtime v0.0.0-20160704190703-11e322eeecc1 - github.com/go-openapi/spec v0.0.0-20180213232550-1de3e0542de6 - github.com/go-openapi/strfmt v0.0.0-20160812050534-d65c7fdb29ec - github.com/go-openapi/swag v0.0.0-20170606142751-f3f9494671f9 - github.com/go-openapi/validate v0.0.0-20171117174350-d509235108fc - github.com/go-stack/stack v1.8.0 - github.com/gobwas/glob v0.2.3 - github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e + github.com/go-kit/kit v0.8.0 + github.com/go-logfmt/logfmt v0.4.0 // indirect + github.com/go-openapi/spec v0.18.0 // indirect + github.com/go-openapi/strfmt v0.18.0 // indirect + github.com/go-openapi/validate v0.18.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/gogo/protobuf v1.2.0 // indirect github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b - github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 - github.com/golang/protobuf v1.2.0 - github.com/google/btree v0.0.0-20160524151835-7d79101e329e - github.com/google/go-github v17.0.0+incompatible - github.com/google/go-jsonnet v0.11.2 - github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 - github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367 - github.com/google/uuid v0.0.0-20171113160352-8c31c18f31ed - github.com/googleapis/gax-go v2.0.0+incompatible - github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d - github.com/gophercloud/gophercloud v0.0.0-20180210024343-6da026c32e2d - github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 - github.com/hashicorp/golang-lru v0.0.0-20160207214719-a0d98a5f2880 - github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c - github.com/huandu/xstrings v0.0.0-20180906151751-8bbcf2f9ccb5 - github.com/imdario/mergo v0.0.0-20141206190957-6633656539c1 - github.com/inconshreveable/mousetrap v1.0.0 - github.com/jonboulle/clockwork v0.0.0-20180716110948-e7c6d408fd5c - github.com/json-iterator/go v0.0.0-20171212105241-13f86432b882 - github.com/kardianos/osext v0.0.0-20150410034420-8fef92e41e22 - github.com/konsorten/go-windows-terminal-sequences v1.0.1 - github.com/kr/fs v0.1.0 - github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 + github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff // indirect + github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect + github.com/google/go-github v17.0.0+incompatible // indirect + github.com/google/go-jsonnet v0.12.1 // indirect + github.com/google/go-querystring v1.0.0 // indirect + github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect + github.com/googleapis/gax-go v2.0.2+incompatible // indirect + github.com/googleapis/gnostic v0.2.0 // indirect + github.com/gophercloud/gophercloud v0.0.0-20190116032514-258a61a0642d // indirect + github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/hashicorp/golang-lru v0.5.0 // indirect + github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c // indirect + github.com/huandu/xstrings v1.2.0 // indirect + github.com/imdario/mergo v0.3.6 + github.com/jonboulle/clockwork v0.1.0 // indirect + github.com/json-iterator/go v1.1.5 // indirect + github.com/juju/ratelimit v1.0.1 // indirect github.com/ksonnet/ksonnet v0.13.1 - github.com/ksonnet/ksonnet-lib v0.1.12 - github.com/mailru/easyjson v0.0.0-20170624190925-2f5df55504eb - github.com/mattn/go-colorable v0.0.0-20180310133214-efa589957cd0 - github.com/mattn/go-isatty v0.0.0-20180830101745-3fb116b82035 - github.com/matttproud/golang_protobuf_extensions v1.0.1 - github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 - github.com/onrik/logrus v0.0.0-20180801161715-ca0a758702be - github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420 - github.com/opencontainers/image-spec v0.0.0-20170604055404-372ad780f634 - github.com/pborman/uuid v0.0.0-20150603214016-ca53cad383ca - github.com/peterbourgon/diskv v2.0.1+incompatible - github.com/pkg/errors v0.8.0 - github.com/pkg/sftp v0.0.0-20180824225003-b9345f483dc3 - github.com/pmezard/go-difflib v1.0.0 - github.com/prometheus/client_golang v0.8.0 - github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 - github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e - github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 - github.com/russross/blackfriday v0.0.0-20151117072312-300106c228d5 - github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c - github.com/shurcooL/sanitized_anchor_name v0.0.0-20151028001915-10ef21a441db - github.com/sirupsen/logrus v1.2.0 - github.com/spf13/afero v0.0.0-20160816080757-b28a7effac97 - github.com/spf13/cobra v0.0.0-20180228053838-6644d46b81fa + github.com/ksonnet/ksonnet-lib v0.1.12 // indirect + github.com/kubernetes/code-generator v0.0.0-20181206115026-3a2206dd6a78 // indirect + github.com/mattn/go-colorable v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.4 // indirect + github.com/mitchellh/go-homedir v1.0.0 + github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d // indirect + github.com/onrik/logrus v0.2.1 + github.com/onsi/ginkgo v1.7.0 // indirect + github.com/onsi/gomega v1.4.3 + github.com/opencontainers/go-digest v1.0.0-rc1 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pkg/errors v0.8.1 // indirect + github.com/prometheus/client_golang v0.9.2 + github.com/russross/blackfriday v1.5.2-0.20180428102519-11635eb403ff // indirect + github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/sirupsen/logrus v1.3.0 + github.com/spf13/afero v1.2.0 + github.com/spf13/cobra v0.0.3 github.com/spf13/pflag v1.0.3 - github.com/stretchr/testify v1.2.2 - go.opencensus.io v0.15.0 - golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 - golang.org/x/net v0.0.0-20180906233101-161cd47e91fd - golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be - golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e - golang.org/x/text v0.3.0 - golang.org/x/time v0.0.0-20161028155119-f51c12702a4d - golang.org/x/vgo v0.0.0-20180912184537-9d567625acf4 // indirect - google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf - google.golang.org/appengine v1.1.0 - google.golang.org/genproto v0.0.0-20180911211118-36d5787dc535 - google.golang.org/grpc v1.16.0 - gopkg.in/inf.v0 v0.9.0 - gopkg.in/square/go-jose.v2 v2.1.3 - gopkg.in/yaml.v2 v2.0.0-20170721113624-670d4cfef054 + github.com/spf13/viper v1.3.1 + golang.org/x/net v0.0.0-20190110200230-915654e7eabc + golang.org/x/oauth2 v0.0.0-20190115181402-5dab4167f31c + golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect + google.golang.org/api v0.1.0 + google.golang.org/genproto v0.0.0-20190111180523-db91494dd46c + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + gopkg.in/resty.v1 v1.11.0 + gopkg.in/square/go-jose.v2 v2.2.2 // indirect + gopkg.in/yaml.v2 v2.2.2 k8s.io/api v0.0.0-20180308224125-73d903622b73 - k8s.io/apiextensions-apiserver v0.0.0-20180908152229-2c1d23e4c7d6 - k8s.io/apimachinery v0.0.0-20180228050457-302974c03f7e - k8s.io/apiserver v0.0.0-20180426121757-0841753fc26e - k8s.io/client-go v7.0.0+incompatible - k8s.io/helm v0.0.0-20180910190057-b4b693c31684 - k8s.io/kube-openapi v0.0.0-20180216212618-50ae88d24ede - k8s.io/kubernetes v1.10.2 - k8s.io/utils v0.0.0-20171122000934-aedf551cdb8b - vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc + k8s.io/apiextensions-apiserver v0.0.0-20190116054503-cf30b7cf64c2 + k8s.io/apimachinery v0.0.0-20190111195121-fa6ddc151d63 + k8s.io/apiserver v0.0.0-20190116053355-a39732bdd925 // indirect + k8s.io/client-go v10.0.0+incompatible + k8s.io/code-generator v0.0.0-20180601180426-9de8e796a74d // indirect + k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af // indirect + k8s.io/helm v2.12.3+incompatible // indirect + k8s.io/klog v0.1.0 // indirect + k8s.io/kube-openapi v0.0.0-20190115222348-ced9eb3070a5 // indirect + k8s.io/kubernetes v1.10.2 // indirect + k8s.io/utils v0.0.0-20181221173059-8a16e7dd8fb6 // indirect + sigs.k8s.io/controller-runtime v0.1.1 + sigs.k8s.io/testing_frameworks v0.1.1 // indirect + sigs.k8s.io/yaml v1.1.0 // indirect + vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect +) + +replace ( + github.com/Azure/go-autorest => github.com/Azure/go-autorest v9.1.0+incompatible + github.com/mitchellh/go-homedir => github.com/mitchellh/go-homedir v1.0.0 + github.com/russross/blackfriday => github.com/russross/blackfriday v1.5.2-0.20180428102519-11635eb403ff // indirect + k8s.io/api => k8s.io/api v0.0.0-20180308224125-73d903622b73 + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20180426153726-e8ab413e0ae1 + k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20180228050457-302974c03f7e + k8s.io/apiserver => k8s.io/apiserver v0.0.0-20180426121757-0841753fc26e // indirect + k8s.io/client-go => github.com/kubernetes/client-go v6.0.0+incompatible + k8s.io/code-generator/cmd/client-gen => k8s.io/code-generator/cmd/client-gen v0.0.0-20180228050103-7ead8f38b01c + k8s.io/kubernetes => k8s.io/kubernetes v1.10.2+incompatible ) diff --git a/bootstrap/go.sum b/bootstrap/go.sum index 3a7db1baca5..08d9872cb3b 100644 --- a/bootstrap/go.sum +++ b/bootstrap/go.sum @@ -1,449 +1,240 @@ -cloud.google.com/go v0.23.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +bitbucket.org/ww/goautoneg v0.0.0-20120707110453-75cd24fc2f2c/go.mod h1:1vhO7Mn/FZMgOgDVGLy5X1mE6rq1HbkBdkF/yj8zkcg= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.32.0 h1:DSt59WoyNcfAInilEpfvm2ugq8zvNyaHAm9MkzOwRQ4= -cloud.google.com/go v0.32.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.33.1 h1:fmJQWZ1w9PGkHR1YL/P7HloDvqlmKQ4Vpb7PC2e+aCk= -cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -contrib.go.opencensus.io/exporter/ocagent v0.3.0 h1:fyqPXp7d+BBV3tXa7EE1CYrObJr7R9jTAOO/AsdcQBg= -contrib.go.opencensus.io/exporter/ocagent v0.3.0/go.mod h1:0fnkYHF+ORKj7HWzOExKkUHeFX79gXSKUQbpnAM+wzo= -contrib.go.opencensus.io/exporter/stackdriver v0.0.0-20180910204836-9f333b48d382 h1:EcxVcuCE9b9df6PKrWYX8aEdA9UU8OxMBMGNge5G5rs= -contrib.go.opencensus.io/exporter/stackdriver v0.0.0-20180910204836-9f333b48d382/go.mod h1:hNe5qQofPbg6bLQY5wHCvQ7o+2E5P8PkegEuQ+MyRw0= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +contrib.go.opencensus.io/exporter/ocagent v0.4.2/go.mod h1:YuG83h+XWwqWjvCqn7vK4KSyLKhThY3+gNGQ37iS2V0= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/Azure/go-ansiterm v0.0.0-20170629204627-19f72df4d05d/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v9.9.0+incompatible h1:U3u8yuM3dZPg6vjJ/Hl2hQlaP+WNOeSHY6Fk4g1e6Ps= -github.com/Azure/go-autorest v9.9.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest v11.2.8+incompatible h1:Q2feRPMlcfVcqz3pF87PJzkm5lZrL+x6BDtzhODzNJM= -github.com/Azure/go-autorest v11.2.8+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v9.1.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da h1:UVU3a9pRUyLdnBtn60WjRl0s4SEyJc2ChCY56OAR6wI= -github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da/go.mod h1:DgrzXonpdQbfN3uYaGz1EG4Sbhyum/MMIn6Cphlh2bw= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/GeertJohan/go.rice v0.0.0-20181229193832-0af3f3b09a0a/go.mod h1:DgrzXonpdQbfN3uYaGz1EG4Sbhyum/MMIn6Cphlh2bw= github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm7f2FFYv7sjY7NPFi3cPkS3tv1CcrFBWA= github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= -github.com/Masterminds/semver v1.3.1 h1:4CEBDLZtuloRJFiIzzlR/VcQOCiFzhaaa7hE4DEB97Y= -github.com/Masterminds/semver v1.3.1/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/sprig v2.16.0+incompatible h1:QZbMUPxRQ50EKAq3LFMnxddMu88/EUUG3qmxwtDmPsY= -github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/PuerkitoBio/purell v1.0.0 h1:0GoNN3taZV6QI81IXgCbxMyEaJDXMSIjArYBCYzVVvs= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= +github.com/Masterminds/sprig v2.17.1+incompatible h1:PChbxFGKTWsg9IWh+pSZRCSj3zQkVpL6Hd9uWsFwxtc= +github.com/Masterminds/sprig v2.17.1+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2 h1:JCHLVE3B+kJde7bIEo5N4J+ZbLhp0J1Fs+ulyRws4gE= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg= -github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= -github.com/asaskevich/govalidator v0.0.0-20160715170612-593d64559f76 h1:4qUqDTa6lMQpizrXsHIcFDpn9gAIia6ovRy8Dgkdpo4= -github.com/asaskevich/govalidator v0.0.0-20160715170612-593d64559f76/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= +github.com/aokoli/goutils v1.1.0 h1:jy4ghdcYvs5EIoGssZNslIASX5m+KNMfyyKvRQ0TEVE= +github.com/aokoli/goutils v1.1.0/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go v1.15.31/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs= -github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/cenkalti/backoff v2.0.0+incompatible h1:5IIPUHhlnUZbcHQsQou5k1Tn58nJkeJL9U+ig5CHJbY= -github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/census-instrumentation/opencensus-proto v0.0.2-0.20180913191712-f303ae3f8d6a h1:t88pXOTS5K+pjfuhTOcul6sdC4khgqB8ukyfbe62Zxo= -github.com/census-instrumentation/opencensus-proto v0.0.2-0.20180913191712-f303ae3f8d6a/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.1.0-0.20181214143942-ba49f56771b8/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb h1:tUf55Po0vzOendQ7NWytcdK0VuzQmfAgvGBUOQvN0WA= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.11+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb/go.mod h1:U0vRfAucUOohvdCxt5MWLF+TePIL0xbCkbKIiV8TQCE= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda h1:NyywMz59neOoVRFDz+ccfKWxn784fiHMDnZSy6T+JXY= -github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/docker/distribution v0.0.0-20170726174610-edc3ab29cdff h1:FKH02LHYqSmeWd3GBh0KIkM8JBpw3RrShgtcWShdWJg= -github.com/docker/distribution v0.0.0-20170726174610-edc3ab29cdff/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.6.2+incompatible h1:4FI6af79dfCS/CYb+RRtkSHw3q1L/bnDjG1PcPZtQhM= -github.com/docker/distribution v2.6.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v0.0.0-20170731201938-4f3616fb1c11 h1:skZo0oeBh5+kqakXZ+BNJb/4tnyJX8VWihqioHRSpVM= -github.com/docker/docker v0.0.0-20170731201938-4f3616fb1c11/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v1.13.1 h1:5VBhsO6ckUxB0A8CE5LlUJdXzik9cbEbBTQ/ggeml7M= +github.com/docker/distribution v2.7.0+incompatible h1:neUDAlf3wX6Ml4HdqTrbcOHXtfRN0TFIwt6YFL7N9RU= +github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o= -github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.0.0-20170127094116-9e638d38cf69 h1:N4WAsrRIb+4U1yIwJO3FMrLnrr61ael894nygpViQTU= -github.com/docker/go-units v0.0.0-20170127094116-9e638d38cf69/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful-swagger12 v0.0.0-20170926063155-7524189396c6 h1:V94anc0ZG3Pa/cAMwP2m1aQW3+/FF8Qmw/GsFyTJAp4= +github.com/emicklei/go-restful v2.8.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful-swagger12 v0.0.0-20170926063155-7524189396c6/go.mod h1:qr0VowGBT4CS4Q8vFF8BSeKz34PuqKGxs/L0IAQA9DQ= -github.com/evanphx/json-patch v0.0.0-20170719203123-944e07253867 h1:+mzRVkqzHnzJLFgH+5RExrZFJOHdH5MXFyjIjaucHok= -github.com/evanphx/json-patch v0.0.0-20170719203123-944e07253867/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc= github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= -github.com/fatih/camelcase v0.0.0-20160318181535-f6a740d52f96 h1:5e8GDOdG6jKeeqNGbR+tlmqhf4vQVs3atTTMEWeEcAk= -github.com/fatih/camelcase v0.0.0-20160318181535-f6a740d52f96/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= -github.com/fatih/color v0.0.0-20180516100307-2d684516a886 h1:uG3h1WD7I3u1FP2+EdJjjhM1A3DKbZuRQz8H5cv6fyE= -github.com/fatih/color v0.0.0-20180516100307-2d684516a886/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-kit/kit v0.7.0 h1:ApufNmWF1H6/wUbAG81hZOHmqwd0zRf8mNfLjYj/064= -github.com/go-kit/kit v0.7.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-openapi/analysis v0.0.0-20160815203709-b44dc874b601 h1:0puMfPt1Uwv6yAyiIm/kkCdxRFURsaHGLuqyBmNTaHU= -github.com/go-openapi/analysis v0.0.0-20160815203709-b44dc874b601/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0 h1:8JV+dzJJiK46XqGLqqLav8ZfEiJECp8jlOFhpiCdZ+0= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/errors v0.0.0-20160704190347-d24ebc2075ba h1:pnZvk2/LMHh8W++YayCRBerbaZ3z1pOYEirKO0tQmNE= -github.com/go-openapi/errors v0.0.0-20160704190347-d24ebc2075ba/go.mod h1:La0D2x9HoXenv7MDEiAv6vWoe84CXFo0PQRk/jdQlww= -github.com/go-openapi/errors v0.17.0 h1:g5DzIh94VpuR/dd6Ff8KqyHNnw7yBa2xSHIPPzjRDUo= github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1 h1:wSt/4CYxs70xbATrGXhokKF1i0tZjENLOo1ioIO13zk= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9 h1:tF+augKRWlWx0J0B7ZyyKSiTyV6E1zZe+7b3qQlcEf8= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/loads v0.0.0-20170520182102-a80dea3052f0 h1:hQ+g9KGJTxSzAbocTc8g7JQ5YKxwVtm88mrFFi3KFM0= -github.com/go-openapi/loads v0.0.0-20170520182102-a80dea3052f0/go.mod h1:5qFWh9T8iTbMizjsoC/EHEN3onRy+cfNRw/wV1iX1Og= -github.com/go-openapi/loads v0.17.0 h1:H22nMs3GDQk4SwAaFQ+jLNw+0xoFeCueawhZlv8MBYs= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/runtime v0.0.0-20160704190703-11e322eeecc1 h1:tGbuViOWiotNueyqn2/UthMwIF7jR7uysB1pzhaKvC0= -github.com/go-openapi/runtime v0.0.0-20160704190703-11e322eeecc1/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9 h1:zXd+rkzHwMIYVTJ/j/v8zUQ9j3Ir32gC5Dn9DzZVvCk= github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/spec v0.0.0-20180213232550-1de3e0542de6 h1:YNRPy5kFpzJs/7Iat7Don4Xk8NQPvJ4td91qnYzE3UM= -github.com/go-openapi/spec v0.0.0-20180213232550-1de3e0542de6/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.17.2 h1:eb2NbuCnoe8cWAxhtK6CfMWUYmiFEZJ9Hx3Z2WRwJ5M= -github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/strfmt v0.0.0-20160812050534-d65c7fdb29ec h1:iDUVvQ2S3KDlzQy7th4XRNuBJC/yor56rUXG4BFwiZk= -github.com/go-openapi/strfmt v0.0.0-20160812050534-d65c7fdb29ec/go.mod h1:/bCWipNKhC9QMhD8HRe2EGbU8G0D4Yvh0G6X4k1Xwvg= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.17.2 h1:2KDns36DMHXG9/iYkOjiX+/8fKK9GCU5ELZ+J6qcRVA= -github.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/swag v0.0.0-20170606142751-f3f9494671f9 h1:4Zsyv/tIS5V+22fc4X0ApWuYL+O+0v75qX1R9rpua9U= -github.com/go-openapi/swag v0.0.0-20170606142751-f3f9494671f9/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/validate v0.0.0-20171117174350-d509235108fc h1:UJyw/53boIXv4pkhbDpn1LGChx/+6g4OTYv/flQ0aMQ= -github.com/go-openapi/validate v0.0.0-20171117174350-d509235108fc/go.mod h1:ve8xoSHgqBUifiKgaVbxLmOE0ckvH0oXfsJcnm6SIz0= -github.com/go-openapi/validate v0.17.2 h1:lwFfiS4sv5DvOrsYDsYq4N7UU8ghXiYtPJ+VcQnC3Xg= -github.com/go-openapi/validate v0.17.2/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e h1:ago6fNuQ6IhszPsXkeU7qRCyfsIX7L67WDybsAPkLl8= -github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff h1:kOkM9whyQYodu09SJ6W3NCsHG7crFaJILQ22Gozp3lg= github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20160524151835-7d79101e329e h1:JHB7F/4TJCrYBW8+GZO8VkWDj1jxcWuCl6uxKODiyi4= -github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-jsonnet v0.11.2 h1:oSCkvGPE72ouWJLOajr4p0clGFCOFmwhqo52faGsudk= -github.com/google/go-jsonnet v0.11.2/go.mod h1:gVu3UVSfOt5fRFq+dh9duBqXa5905QY8S1QvMNcEIVs= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-jsonnet v0.12.1/go.mod h1:gVu3UVSfOt5fRFq+dh9duBqXa5905QY8S1QvMNcEIVs= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367 h1:ScAXWS+TR6MZKex+7Z8rneuSJH+FSDqd6ocQyl+ZHo4= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/uuid v0.0.0-20171113160352-8c31c18f31ed h1:Szj6KEFeGO1W5ymzI4Tr+VlYPtPWGPGeupR/Ym2i5uI= -github.com/google/uuid v0.0.0-20171113160352-8c31c18f31ed/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= -github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= -github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gophercloud/gophercloud v0.0.0-20180210024343-6da026c32e2d h1:lciDD/FphWa1txj6lYLOJCvV6IcxJcT2dfgg9JLXd+c= -github.com/gophercloud/gophercloud v0.0.0-20180210024343-6da026c32e2d/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= -github.com/gophercloud/gophercloud v0.0.0-20181114204705-3a7818a07cfc h1:RNklV04vzP/0d81WOsqhMa98tkSEBiw6fdI2AfIV1r4= -github.com/gophercloud/gophercloud v0.0.0-20181114204705-3a7818a07cfc/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= -github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 h1:6TSoaYExHper8PYsJu23GWVNOyYRCSnIFyxKgLSZ54w= -github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6tubcjzGMODIVG5hlrCeImaBnZzKF2N8SM= +github.com/gophercloud/gophercloud v0.0.0-20190116032514-258a61a0642d/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/hashicorp/golang-lru v0.0.0-20160207214719-a0d98a5f2880 h1:OaRuzt9oCKNui8cCskZijoKUwe+aCuuCwvx1ox8FNyw= -github.com/hashicorp/golang-lru v0.0.0-20160207214719-a0d98a5f2880/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c h1:kQWxfPIHVLbgLzphqk3QUflDy9QdksZR4ygR807bpy0= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= -github.com/huandu/xstrings v0.0.0-20180906151751-8bbcf2f9ccb5 h1:JpFoh6mXPEoUpIgH3hg22MgQg+qwMU1eGdnWUII9csA= -github.com/huandu/xstrings v0.0.0-20180906151751-8bbcf2f9ccb5/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= -github.com/imdario/mergo v0.0.0-20141206190957-6633656539c1 h1:FeeCi0I2Fu8kA8IXrdVPtGzym+mW9bzfj9f26EaES9k= -github.com/imdario/mergo v0.0.0-20141206190957-6633656539c1/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jonboulle/clockwork v0.0.0-20180716110948-e7c6d408fd5c h1:P2BrTyxPK1DQLq5+Y5LQ0BWli1oO+ebRi7J6ojFhykE= -github.com/jonboulle/clockwork v0.0.0-20180716110948-e7c6d408fd5c/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v0.0.0-20171212105241-13f86432b882 h1:Xqf4pbZo/Iztd32Rrir1VZsAvz7r85JmWPHR95V+Flo= -github.com/json-iterator/go v0.0.0-20171212105241-13f86432b882/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/kardianos/osext v0.0.0-20150410034420-8fef92e41e22 h1:eLCQd4nxsC7sumkwNg4OiB6bGiD7I5l1MSfBAxmxkKQ= -github.com/kardianos/osext v0.0.0-20150410034420-8fef92e41e22/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro= -github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/ksonnet/ksonnet v0.12.0/go.mod h1:T8FwyGOX8fMoDyp845QmYDnk8R7AF35pImyfWJ9fj7M= -github.com/ksonnet/ksonnet v0.13.1 h1:SoMiL8+ZkWBwyqOOQIt/9nh1Yus5d8oe7sczQCMXsAg= github.com/ksonnet/ksonnet v0.13.1/go.mod h1:T8FwyGOX8fMoDyp845QmYDnk8R7AF35pImyfWJ9fj7M= -github.com/ksonnet/ksonnet-lib v0.1.10/go.mod h1:2p66Npe1xOUtQGlGzD5aJ3UHEBjG1b6o3nbC1rVNvz4= -github.com/ksonnet/ksonnet-lib v0.1.12 h1:dudu+eYkbWD1s8BlI6ShvHHJDRbOeF9Z+LEQqdcjmVU= github.com/ksonnet/ksonnet-lib v0.1.12/go.mod h1:2p66Npe1xOUtQGlGzD5aJ3UHEBjG1b6o3nbC1rVNvz4= -github.com/mailru/easyjson v0.0.0-20170624190925-2f5df55504eb h1:D5TlrOFWiDz5VIdPnkPwyIRIgStzH3wBBS7v1O1cfSY= -github.com/mailru/easyjson v0.0.0-20170624190925-2f5df55504eb/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= +github.com/kubernetes/client-go v6.0.0+incompatible/go.mod h1:kszVi2i+FeqECZHhjpkV5h5zM0GnURfJv897YzgoAQ8= +github.com/kubernetes/code-generator v0.0.0-20181206115026-3a2206dd6a78/go.mod h1:GXXF2gdS/LDGLo7HilXGuIQfo5nLQDhB4eUn/2UMPnk= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mattn/go-colorable v0.0.0-20180310133214-efa589957cd0 h1:cDvUG90i1ssGJGqMNx2Ubbn+bx7VOzjdvQ45zpy0X4w= -github.com/mattn/go-colorable v0.0.0-20180310133214-efa589957cd0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.0-20180830101745-3fb116b82035 h1:Axpq75UxrWIEGxxu1s93yPE9VBqKg7swkJwF5kXxRuA= -github.com/mattn/go-isatty v0.0.0-20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/onrik/logrus v0.0.0-20180801161715-ca0a758702be h1:iTlQZJpiFmWDq1ZJDiB4L9S2xcVwShR3Olu0cFfpppw= -github.com/onrik/logrus v0.0.0-20180801161715-ca0a758702be/go.mod h1:qfe9NeZVAJfIxviw3cYkZo3kvBtLoPRJriAO8zl7qTk= -github.com/onrik/logrus v0.0.0-20181009124311-c9849815bc7c h1:nzLY8VSbMnY29mjrTj8BjiC4VM3tTCfYhJjr27ZVLn4= -github.com/onrik/logrus v0.0.0-20181009124311-c9849815bc7c/go.mod h1:qfe9NeZVAJfIxviw3cYkZo3kvBtLoPRJriAO8zl7qTk= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420 h1:Yu3681ykYHDfLoI6XVjL4JWmkE+3TX9yfIWwRCh1kFM= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/image-spec v0.0.0-20170604055404-372ad780f634 h1:BNgUWy7fCNMkfpyG05/9wWeDnIY4hqs9UpqkGIjAb68= -github.com/opencontainers/image-spec v0.0.0-20170604055404-372ad780f634/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onrik/logrus v0.2.1/go.mod h1:qfe9NeZVAJfIxviw3cYkZo3kvBtLoPRJriAO8zl7qTk= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/pborman/uuid v0.0.0-20150603214016-ca53cad383ca h1:dKRMHfduZ/ZqOHuYGk/0kkTIUbnyorkAfzLOp6Ts8pU= -github.com/pborman/uuid v0.0.0-20150603214016-ca53cad383ca/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v0.0.0-20180824225003-b9345f483dc3 h1:WoQsKxLkGC71OjqWitPdENKSndT8B/AoO6Tr+BJPLeY= -github.com/pkg/sftp v0.0.0-20180824225003-b9345f483dc3/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.1 h1:K47Rk0v/fkEfwfQet2KWhscE0cJzjgCCDBG2KHZoVno= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181126161756-619930b0b471 h1:yLaU2uaatUJcbMacSwa4Dvstrkh6GXm2LhIpo0eGAms= -github.com/prometheus/procfs v0.0.0-20181126161756-619930b0b471/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/russross/blackfriday v0.0.0-20151117072312-300106c228d5 h1:+6eORf9Bt4C3Wjt91epyu6wvLW+P6+AEODb6uKgO+4g= -github.com/russross/blackfriday v0.0.0-20151117072312-300106c228d5/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk= -github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/russross/blackfriday v1.5.2-0.20180428102519-11635eb403ff h1:bxenFOpdnKzbA1dhcJpgiwjSw7yqvWWY6huCpmsBfv0= +github.com/russross/blackfriday v1.5.2-0.20180428102519-11635eb403ff/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= -github.com/shurcooL/sanitized_anchor_name v0.0.0-20151028001915-10ef21a441db h1:lrOUn8raSZS/V52c7elGaEyuogqSkEo/Qj2Auo2G1ik= -github.com/shurcooL/sanitized_anchor_name v0.0.0-20151028001915-10ef21a441db/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY= -github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/spf13/afero v0.0.0-20160816080757-b28a7effac97 h1:Gv1HykSEG+RKWWWkM69nPrJKhE/EM2oFb1nBWogHNv8= -github.com/spf13/afero v0.0.0-20160816080757-b28a7effac97/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cobra v0.0.0-20180228053838-6644d46b81fa h1:w+PYzMV4Hrxj0nSHUxY881YFte8hst14ZZ0ZNL3mlEA= -github.com/spf13/cobra v0.0.0-20180228053838-6644d46b81fa/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/spf13/afero v1.2.0/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -go.opencensus.io v0.15.0 h1:r1SzcjSm4ybA0qZs3B4QYX072f8gK61Kh0qtwyFpfdk= -go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= -go.opencensus.io v0.17.0/go.mod h1:mp1VrMQxhlqqDpKvH4UcQUa4YwlzNmymAjPrDdfxNpI= -go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= +go.opencensus.io v0.18.1-0.20181204023538-aab39bd6a98b/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/oauth2 v0.0.0-20180603041954-1e0a3fa8ba9a/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba h1:YDkOrzGLLYybtuP6ZgebnO4OWYEYVMFSniazXsxrFN8= -golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190115181402-5dab4167f31c/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g= -golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/vgo v0.0.0-20180912184537-9d567625acf4/go.mod h1:rPwukS3Aj8LZv1dK2rFNXSfYxdueYuvBJ7u3uL3f47I= -google.golang.org/api v0.0.0-20180603000442-8e296ef26005/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf h1:rjxqQmxjyqerRKEj+tZW+MCm4LgpFXu18bsEoCMgDsk= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20181126234655-bed42c95df7d h1:gnVrknQY8QFJ07E1fALKqnJh7vs/gtGpIIZR1S5a+/k= -google.golang.org/api v0.0.0-20181126234655-bed42c95df7d/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/genproto v0.0.0-20180601223552-81158efcc9f2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180911211118-36d5787dc535 h1:4lW+9ga4DVNp64aINPlAqqle9EB/P4t+A7pL9kzzrQc= -google.golang.org/genproto v0.0.0-20180911211118-36d5787dc535/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b h1:WkFtVmaZoTRVoRYr0LTC9SYNhlw0X0HrVPz2OVssVm4= -google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= -google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190111180523-db91494dd46c/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= -google.golang.org/grpc v1.16.0 h1:dz5IJGuC2BB7qXR5AyHNwAUBhZscK2xVez7mznh72sY= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o= -gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/square/go-jose.v2 v2.1.3 h1:/FoFBTvlJN6MTTVCe9plTOG+YydzkjvDGxiSPzIyoDM= -gopkg.in/square/go-jose.v2 v2.1.3/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.1.9 h1:YCFbL5T2gbmC2sMG12s1x2PAlTK5TZNte3hjZEIcCAg= -gopkg.in/square/go-jose.v2 v2.1.9/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/yaml.v2 v2.0.0-20170721113624-670d4cfef054 h1:ROF+R/wHHruzF40n5DfPv2jwm7rCJwvs8fz+RTZWjLE= -gopkg.in/yaml.v2 v2.0.0-20170721113624-670d4cfef054/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.11.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.0.0-20180308224125-73d903622b73 h1:5Z+PFfTIOXwKmOhQtZ0WBykbpGBBOuvbDx2YNAqIoYc= k8s.io/api v0.0.0-20180308224125-73d903622b73/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= -k8s.io/api v0.0.0-20181121071145-b7bd5f2d334c h1:aSW17ws1n3Y/gxcAggEFSs+UJlzpE3+stTPLQSiVEno= -k8s.io/api v0.0.0-20181121071145-b7bd5f2d334c/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= -k8s.io/apiextensions-apiserver v0.0.0-20180908152229-2c1d23e4c7d6 h1:j0cpLeZLbX4ZWtc7cs1g+LJQzatwx5JrMpLqArVkk/8= -k8s.io/apiextensions-apiserver v0.0.0-20180908152229-2c1d23e4c7d6/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= -k8s.io/apiextensions-apiserver v0.0.0-20181121072900-e8a638592964 h1:xXmxmtvxdq31qsBaNCZtQ28d9pu01EWUbXDQmMmCM6Q= -k8s.io/apiextensions-apiserver v0.0.0-20181121072900-e8a638592964/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= -k8s.io/apimachinery v0.0.0-20180228050457-302974c03f7e h1:CsgbEA8905OlpVLNKWD4GacPex50kFbqhotVNPew+dU= +k8s.io/apiextensions-apiserver v0.0.0-20180426153726-e8ab413e0ae1 h1:7i7e3Zai1/Zxgf3Ang0Yr89hyd9/Nlrfx63jyP3blpI= +k8s.io/apiextensions-apiserver v0.0.0-20180426153726-e8ab413e0ae1/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= k8s.io/apimachinery v0.0.0-20180228050457-302974c03f7e/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= -k8s.io/apimachinery v0.0.0-20181126191516-4a9a8137c0a1 h1:u/v3rSGNjiTxclqUNHYgSrCIotyczPebwV1FPXtdKRQ= -k8s.io/apimachinery v0.0.0-20181126191516-4a9a8137c0a1/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= k8s.io/apiserver v0.0.0-20180426121757-0841753fc26e h1:Kx5WFa3bGbM4WoxVI4q+yQ21RVnq+nG5DDF+GMGLNqY= k8s.io/apiserver v0.0.0-20180426121757-0841753fc26e/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w= -k8s.io/apiserver v0.0.0-20181121231732-e3c8fa95bba5 h1:l055lCDDjCXSb3HIk0E0IJaorhEUpvkOgSqXlyBwtKI= -k8s.io/apiserver v0.0.0-20181121231732-e3c8fa95bba5/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w= -k8s.io/cli-runtime v0.0.0-20181121073402-2f0d1d0a58f2 h1:0tWjdH70/BhNHxQ1cc0DEO6iogWpNoY4dYRzUtwg+/g= -k8s.io/cli-runtime v0.0.0-20181121073402-2f0d1d0a58f2/go.mod h1:qWnH3/b8sp/l7EvlDh7ulDU3UWA4P4N1NFbEEP791tM= -k8s.io/client-go v7.0.0+incompatible h1:kiH+Y6hn+pc78QS/mtBfMJAMIIaWevHi++JvOGEEQp4= -k8s.io/client-go v7.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= -k8s.io/client-go v9.0.0+incompatible h1:2kqW3X2xQ9SbFvWZjGEHBLlWc1LG9JIJNXWkuqwdZ3A= -k8s.io/client-go v9.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= -k8s.io/helm v0.0.0-20180910190057-b4b693c31684 h1:KK9zOh2Hrb+bzQqHQmbIkRB5oF4SwgPmqQHJ7vt3Flc= -k8s.io/helm v0.0.0-20180910190057-b4b693c31684/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= -k8s.io/helm v2.11.0+incompatible h1:MsUJ70F6ViBBmcy4IgCAgvRSteMO2qpPCb3mAZ9Y850= -k8s.io/helm v2.11.0+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= -k8s.io/klog v0.1.0 h1:I5HMfc/DtuVaGR1KPwUrTc476K8NCqNBldC7H4dYEzk= +k8s.io/code-generator v0.0.0-20180601180426-9de8e796a74d/go.mod h1:MYiN+ZJZ9HkETbgVZdWw2AsuAi9PZ4V80cwfuf2axe8= +k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/helm v2.12.3+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/kube-openapi v0.0.0-20180216212618-50ae88d24ede h1:YOWlONzJUq456SnNYPcK/org5asA+LU6AzNBm+l/04o= -k8s.io/kube-openapi v0.0.0-20180216212618-50ae88d24ede/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= -k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be h1:aWEq4nbj7HRJ0mtKYjNSk/7X28Tl6TI6FeG8gKF+r7Q= -k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= -k8s.io/kubernetes v1.10.2 h1:LRXf0P/+l+C2KKTpPSeOQNxF9/9Oi4HRLMsdBPoi2/Y= -k8s.io/kubernetes v1.10.2/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/kubernetes v1.12.3 h1:1FH8TXY6pIuOHoNlsq9bB6rmCP0vfvQZH5YN7OXCHXU= -k8s.io/kubernetes v1.12.3/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/utils v0.0.0-20171122000934-aedf551cdb8b h1:yYdRs/7M4OsJDkTfxQnktt/tvrqauKJXUNgsZ/mKtbg= -k8s.io/utils v0.0.0-20171122000934-aedf551cdb8b/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= -k8s.io/utils v0.0.0-20181115163542-0d26856f57b3 h1:S3/Kq185JnolOEemhmDXXd23l2t4bX5hPQPQPADlF1E= -k8s.io/utils v0.0.0-20181115163542-0d26856f57b3/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= -sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= +k8s.io/kube-openapi v0.0.0-20190115222348-ced9eb3070a5/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kubernetes v1.10.2+incompatible h1:7HdUQB9WQUY4vNed/xJ7E14rpstEE6xIco9PrLbaMG4= +k8s.io/kubernetes v1.10.2+incompatible/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/utils v0.0.0-20181221173059-8a16e7dd8fb6 h1:+jRzzMyx+I9J18BvwHYmZ5hpPwoZfh6g39WfNlsMCkY= +k8s.io/utils v0.0.0-20181221173059-8a16e7dd8fb6/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= +sigs.k8s.io/controller-runtime v0.1.1/go.mod h1:HFAYoOh6XMV+jKF1UjFwrknPbowfyHEHHRdJMf2jMX8= +sigs.k8s.io/testing_frameworks v0.1.1 h1:cP2l8fkA3O9vekpy5Ks8mmA0NW/F7yBdXf8brkWhVrs= +sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc h1:MksmcCZQWAQJCTA5T0jgI/0sJ51AVm4Z41MrmfczEoc= -vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 h1:O69FD9pJA4WUZlEwYatBEEkRWKQ5cKodWpdKTrCS/iQ= vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/bootstrap/hack/boilerplate.go.txt b/bootstrap/hack/boilerplate.go.txt new file mode 100644 index 00000000000..87ae5c5a4f1 --- /dev/null +++ b/bootstrap/hack/boilerplate.go.txt @@ -0,0 +1,13 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. diff --git a/bootstrap/pkg/apis/addtoscheme_apps_v1alpha1.go b/bootstrap/pkg/apis/addtoscheme_apps_v1alpha1.go new file mode 100644 index 00000000000..df86cc9302d --- /dev/null +++ b/bootstrap/pkg/apis/addtoscheme_apps_v1alpha1.go @@ -0,0 +1,24 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apis + +import ( + "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps/ksapp/v1alpha1" +) + +func init() { + // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back + AddToSchemes = append(AddToSchemes, v1alpha1.SchemeBuilder.AddToScheme) +} diff --git a/bootstrap/pkg/apis/apis.go b/bootstrap/pkg/apis/apis.go new file mode 100644 index 00000000000..b50a6b14560 --- /dev/null +++ b/bootstrap/pkg/apis/apis.go @@ -0,0 +1,31 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Generate deepcopy for apis +//go:generate deepcopy-gen -O zz_generated.deepcopy -i ./... -h ../../hack/boilerplate.go.txt + +// Package apis contains Kubernetes API groups. +package apis + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +// AddToSchemes may be used to add all resources defined in the project to a Scheme +var AddToSchemes runtime.SchemeBuilder + +// AddToScheme adds all Resources to the Scheme +func AddToScheme(s *runtime.Scheme) error { + return AddToSchemes.AddToScheme(s) +} diff --git a/bootstrap/pkg/apis/apps/group.go b/bootstrap/pkg/apis/apps/group.go new file mode 100644 index 00000000000..7eb39c147a4 --- /dev/null +++ b/bootstrap/pkg/apis/apps/group.go @@ -0,0 +1,178 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package apps contains apps API versions +package apps + +import ( + "fmt" + log "github.com/sirupsen/logrus" + "io" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "os" + "path/filepath" + "regexp" + "strings" +) + +const ( + DefaultNamespace = "kubeflow" + DefaultPlatform = "none" + // TODO: find the latest tag dynamically + DefaultVersion = "v0.4.1" + DefaultKfRepo = "$GOPATH/src/github.com/kubeflow/kubeflow/kubeflow" + KfConfigFile = "app.yaml" +) + +type ResourceEnum string + +const ( + ALL ResourceEnum = "all" + K8S ResourceEnum = "k8s" + PLATFORM ResourceEnum = "platform" + NONE ResourceEnum = "none" +) + +type CliOption string + +const ( + EMAIL CliOption = "email" + IPNAME CliOption = "ipName" + MOUNT_LOCAL CliOption = "mount-local" + VERBOSE CliOption = "verbose" + NAMESPACE CliOption = "namespace" + VERSION CliOption = "version" + REPO CliOption = "repo" + PROJECT CliOption = "project" + APPNAME CliOption = "appname" + APPDIR CliOption = "appDir" + KAPP CliOption = "KApp" + KSAPP CliOption = "KsApp" +) + +// +// KfApp is used by commands under bootstrap/cmd/{bootstrap,kfctl}. KfApp provides a common +// API for different implementations like KsApp, GcpApp, MinikubeApp, etc. +// +type KfApp interface { + Apply(resources ResourceEnum, options map[string]interface{}) error + Delete(resources ResourceEnum, options map[string]interface{}) error + Generate(resources ResourceEnum, options map[string]interface{}) error + Init(options map[string]interface{}) error +} + +func KubeConfigPath() string { + kubeconfigEnv := os.Getenv("KUBECONFIG") + if kubeconfigEnv == "" { + home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + if home == "" { + for _, h := range []string{"HOME", "USERPROFILE"} { + if home = os.Getenv(h); home != "" { + break + } + } + } + kubeconfigPath := filepath.Join(home, ".kube", "config") + return kubeconfigPath + } + return kubeconfigEnv +} + +// BuildOutOfClusterConfig returns k8s config +func BuildOutOfClusterConfig() (*rest.Config, error) { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + loadingRules.ExplicitPath = KubeConfigPath() + config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + loadingRules, &clientcmd.ConfigOverrides{}).ClientConfig() + if err != nil { + return nil, err + } + return config, nil +} + +func ServerVersion() (host string, version string, err error) { + restApi, err := BuildOutOfClusterConfig() + if err != nil { + return "", "", fmt.Errorf("couldn't build out-of-cluster config. Error: %v", err) + } + clnt, clntErr := kubernetes.NewForConfig(restApi) + if clntErr != nil { + return "", "", fmt.Errorf("couldn't get clientset. Error: %v", err) + } + serverVersion, serverVersionErr := clnt.ServerVersion() + if serverVersionErr != nil { + return "", "", fmt.Errorf("couldn't get server version info. Error: %v", serverVersionErr) + } + re := regexp.MustCompile("^v[0-9]+.[0-9]+.[0-9]+") + version = re.FindString(serverVersion.String()) + return restApi.Host, "version:" + version, nil +} + +func GetClientConfig() (*clientcmdapi.Config, error) { + kubeconfig := KubeConfigPath() + config, configErr := clientcmd.LoadFromFile(kubeconfig) + if configErr != nil { + return nil, fmt.Errorf("could not load config Error: %v", configErr) + + } + return config, nil +} + +// GetClientOutOfCluster returns a k8s clientset to the request from outside of cluster +func GetClientOutOfCluster() (kubernetes.Interface, error) { + config, err := BuildOutOfClusterConfig() + if err != nil { + log.Fatalf("Can not get kubernetes config: %v", err) + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + log.Fatalf("Can not get kubernetes client: %v", err) + } + + return clientset, nil +} + +// capture replaces os.Stdout with a writer that buffers any data written +// to os.Stdout. Call the returned function to cleanup and get the data +// as a string. +func capture() func() (string, error) { + r, w, err := os.Pipe() + if err != nil { + panic(err) + } + + done := make(chan error, 1) + + save := os.Stdout + os.Stdout = w + + var buf strings.Builder + + go func() { + _, err := io.Copy(&buf, r) + _ = r.Close() + done <- err + }() + + return func() (string, error) { + os.Stdout = save + _ = w.Close() + err := <-done + return buf.String(), err + } +} diff --git a/bootstrap/pkg/apis/apps/ksapp/app.go b/bootstrap/pkg/apis/apps/ksapp/app.go new file mode 100644 index 00000000000..5b1804f761d --- /dev/null +++ b/bootstrap/pkg/apis/apps/ksapp/app.go @@ -0,0 +1,16 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package ksapps contains ksonnet related types +package ksapp diff --git a/bootstrap/pkg/apis/apps/ksapp/v1alpha1/application_types.go b/bootstrap/pkg/apis/apps/ksapp/v1alpha1/application_types.go new file mode 100644 index 00000000000..ceee66b55ca --- /dev/null +++ b/bootstrap/pkg/apis/apps/ksapp/v1alpha1/application_types.go @@ -0,0 +1,255 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1alpha1 + +import ( + "fmt" + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "math/rand" +) + +const ( + KsName = "ks_app" + KsEnvName = "default" +) + +func QuoteItems(items []string) []string { + withQuotes := []string{} + for _, item := range items { + withQuote := "\"" + item + "\"" + withQuotes = append(withQuotes, withQuote) + } + return withQuotes +} + +func RemoveItem(defaults []string, name string) []string { + pkgs := []string{} + for _, pkg := range defaults { + if pkg != name { + pkgs = append(pkgs, pkg) + } + } + return pkgs +} + +func RemoveItems(defaults []string, names ...string) []string { + pkgs := make([]string, len(defaults)) + copy(pkgs, defaults) + for _, name := range names { + pkgs = RemoveItem(pkgs, name) + } + return pkgs +} + +var DefaultRegistry = &RegistryConfig{ + Name: "kubeflow", + Repo: "https://github.com/kubeflow/kubeflow.git", + Path: "kubeflow", +} + +var DefaultPackages = []string{ + "application", + "argo", + "common", + "examples", + "jupyter", + "katib", + "metacontroller", + "modeldb", + "mpi-job", + "openvino", + "pipeline", + "profiles", + "pytorch-job", + "seldon", + "tensorboard", + "tf-serving", + "tf-training", +} +var DefaultComponents = []string{ + "ambassador", + "application", + "argo", + "centraldashboard", + "jupyter", + "katib", + "metacontroller", + "notebooks", + "openvino", + "pipeline", + "profiles", + "pytorch-operator", + "spartakus", + "tensorboard", + "tf-job-operator", +} + +var DefaultParameters = map[string][]NameValue{ + "spartakus": { + NameValue{ + Name: "usageId", + Value: fmt.Sprintf("%08d", 10000000+rand.Intn(90000000)), + }, + NameValue{ + Name: "reportUsage", + Value: "true", + }, + }, +} + +// RegistryConfig is used for two purposes: +// 1. used during image build, to configure registries that should be baked into the bootstrapper docker image. +// (See: https://github.com/kubeflow/kubeflow/blob/master/bootstrap/image_registries.yaml) +// 2. used during app create rpc call, specifies a registry to be added to an app. +// required info for registry: Name, Repo, Version, Path +// Additionally if any of required fields is blank we will try to map with one of +// the registries baked into the Docker image using the name. +type RegistryConfig struct { + Name string `json:"name,omitempty"` + Repo string `json:"repo,omitempty"` + Version string `json:"version,omitempty"` + Path string `json:"path,omitempty"` + RegUri string `json:"reguri,omitempty"` +} + +type KsComponent struct { + Name string `json:"name,omitempty"` + Prototype string `json:"prototype,omitempty"` +} + +type KsLibrary struct { + Name string `json:"name"` + Registry string `json:"registry"` + Version string `json:"version"` +} + +type KsParameter struct { + // nested components are referenced as "a.b.c" where "a" or "b" may be a module name + Component string `json:"component,omitempty"` + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` +} + +type KsModule struct { + Name string `json:"name"` + Components []*KsComponent `json:"components,omitempty"` + Modules []*KsModule `json:"modules,omitempty"` +} + +type KsPackage struct { + Name string `json:"name,omitempty"` + // Registry should be the name of the registry containing the package. + Registry string `json:"registry,omitempty"` +} + +type Registry struct { + // Name is the user defined name of a registry. + Name string `json:"-"` + // Protocol is the registry protocol for this registry. Currently supported + // values are `github`, `fs`, `helm`. + Protocol string `json:"protocol"` + // URI is the location of the registry. + URI string `json:"uri"` +} + +type LibrarySpec struct { + Version string + Path string +} + +// KsRegistry corresponds to ksonnet.io/registry +// which is the registry.yaml file found in every registry. +type KsRegistry struct { + ApiVersion string + Kind string + Libraries map[string]LibrarySpec +} + +// RegistriesConfigFile corresponds to a YAML file specifying information +// about known registries. +type RegistriesConfigFile struct { + // Registries provides information about known registries. + Registries []*RegistryConfig +} + +type AppConfig struct { + Registries []*RegistryConfig `json:"registries,omitempty"` + Packages []KsPackage `json:"packages,omitempty"` + Components []KsComponent `json:"components,omitempty"` + Parameters []KsParameter `json:"parameters,omitempty"` +} + +type NameValue struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` +} + +// KsAppSpec defines the desired state of KsApp +type KsAppSpec struct { + Platform string `json:"platform,omitempty"` + Version string `json:"version,omitempty"` + Repo string `json:"repo,omitempty"` + Components []string `json:"components,omitempty"` + Packages []string `json:"packages,omitempty"` + Parameters map[string][]NameValue `json:"parameters,omitempty"` +} + +// KsAppStatus defines the observed state of KsApp +type KsAppStatus struct { + Conditions []KsAppCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,6,rep,name=conditions"` +} + +type KsAppConditionType string + +type KsAppCondition struct { + // Type of deployment condition. + Type KsAppConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=KsAppConditionType"` + // Status of the condition, one of True, False, Unknown. + Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` + // The last time this condition was updated. + LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty" protobuf:"bytes,6,opt,name=lastUpdateTime"` + // Last time the condition transitioned from one status to another. + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,7,opt,name=lastTransitionTime"` + // The reason for the condition's last transition. + Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` + // A human readable message indicating details about the transition. + Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// KsApp is the Schema for the applications API +// +k8s:openapi-gen=true +type KsApp struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec KsAppSpec `json:"spec,omitempty"` + Status KsAppStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// KsAppList contains a list of KsApp +type KsAppList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []KsApp `json:"items"` +} + +func init() { + SchemeBuilder.Register(&KsApp{}, &KsAppList{}) +} diff --git a/bootstrap/pkg/apis/apps/ksapp/v1alpha1/application_types_test.go b/bootstrap/pkg/apis/apps/ksapp/v1alpha1/application_types_test.go new file mode 100644 index 00000000000..105e5fb896b --- /dev/null +++ b/bootstrap/pkg/apis/apps/ksapp/v1alpha1/application_types_test.go @@ -0,0 +1,56 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1alpha1 + +import ( + "testing" + + "github.com/onsi/gomega" + "golang.org/x/net/context" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestStorageApplication(t *testing.T) { + key := types.NamespacedName{ + Name: "foo", + Namespace: "default", + } + created := &KsApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "default", + }} + g := gomega.NewGomegaWithT(t) + + // Test Create + fetched := &KsApp{} + g.Expect(c.Create(context.TODO(), created)).NotTo(gomega.HaveOccurred()) + + g.Expect(c.Get(context.TODO(), key, fetched)).NotTo(gomega.HaveOccurred()) + g.Expect(fetched).To(gomega.Equal(created)) + + // Test Updating the Labels + updated := fetched.DeepCopy() + updated.Labels = map[string]string{"hello": "world"} + g.Expect(c.Update(context.TODO(), updated)).NotTo(gomega.HaveOccurred()) + + g.Expect(c.Get(context.TODO(), key, fetched)).NotTo(gomega.HaveOccurred()) + g.Expect(fetched).To(gomega.Equal(updated)) + + // Test Delete + g.Expect(c.Delete(context.TODO(), fetched)).NotTo(gomega.HaveOccurred()) + g.Expect(c.Get(context.TODO(), key, fetched)).To(gomega.HaveOccurred()) +} diff --git a/bootstrap/pkg/apis/apps/ksapp/v1alpha1/doc.go b/bootstrap/pkg/apis/apps/ksapp/v1alpha1/doc.go new file mode 100644 index 00000000000..34507278fc6 --- /dev/null +++ b/bootstrap/pkg/apis/apps/ksapp/v1alpha1/doc.go @@ -0,0 +1,22 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package v1alpha1 contains API Schema definitions for the ksapp v1alpha1 API group +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package,register +// +k8s:conversion-gen=github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps/ksapp +// +k8s:defaulter-gen=TypeMeta +// +groupName=ksapp.apps.kubeflow.org + +package v1alpha1 diff --git a/bootstrap/pkg/apis/apps/ksapp/v1alpha1/register.go b/bootstrap/pkg/apis/apps/ksapp/v1alpha1/register.go new file mode 100644 index 00000000000..aafd8973699 --- /dev/null +++ b/bootstrap/pkg/apis/apps/ksapp/v1alpha1/register.go @@ -0,0 +1,44 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// NOTE: Boilerplate only. Ignore this file. + +// Package v1alpha1 contains API Schema definitions for the ksapp v1alpha1 API group +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package,register +// +k8s:conversion-gen=github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps/ksapp +// +k8s:defaulter-gen=TypeMeta +// +groupName=ksapp.apps.kubeflow.org +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/runtime/scheme" +) + +var ( + // SchemeGroupVersion is group version used to register these objects + SchemeGroupVersion = schema.GroupVersion{Group: "ksapp.apps.kubeflow.org", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} + + // AddToScheme is required by pkg/client/... + AddToScheme = SchemeBuilder.AddToScheme +) + +// Resource is required by pkg/client/listers/... +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} diff --git a/bootstrap/pkg/apis/apps/ksapp/v1alpha1/v1alpha1_suite_test.go b/bootstrap/pkg/apis/apps/ksapp/v1alpha1/v1alpha1_suite_test.go new file mode 100644 index 00000000000..598858167ee --- /dev/null +++ b/bootstrap/pkg/apis/apps/ksapp/v1alpha1/v1alpha1_suite_test.go @@ -0,0 +1,53 @@ +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1alpha1 + +import ( + "log" + "os" + "path/filepath" + "testing" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" +) + +var cfg *rest.Config +var c client.Client + +func TestMain(m *testing.M) { + t := &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crds")}, + } + + err := SchemeBuilder.AddToScheme(scheme.Scheme) + if err != nil { + log.Fatal(err) + } + + if cfg, err = t.Start(); err != nil { + log.Fatal(err) + } + + if c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}); err != nil { + log.Fatal(err) + } + + code := m.Run() + t.Stop() + os.Exit(code) +} diff --git a/bootstrap/pkg/apis/apps/ksapp/v1alpha1/zz_generated.deepcopy.go b/bootstrap/pkg/apis/apps/ksapp/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..391e2401137 --- /dev/null +++ b/bootstrap/pkg/apis/apps/ksapp/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,424 @@ +// +build !ignore_autogenerated + +// Copyright 2018 The Kubeflow Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppConfig) DeepCopyInto(out *AppConfig) { + *out = *in + if in.Registries != nil { + in, out := &in.Registries, &out.Registries + *out = make([]*RegistryConfig, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(RegistryConfig) + **out = **in + } + } + } + if in.Packages != nil { + in, out := &in.Packages, &out.Packages + *out = make([]KsPackage, len(*in)) + copy(*out, *in) + } + if in.Components != nil { + in, out := &in.Components, &out.Components + *out = make([]KsComponent, len(*in)) + copy(*out, *in) + } + if in.Parameters != nil { + in, out := &in.Parameters, &out.Parameters + *out = make([]KsParameter, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppConfig. +func (in *AppConfig) DeepCopy() *AppConfig { + if in == nil { + return nil + } + out := new(AppConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KsApp) DeepCopyInto(out *KsApp) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KsApp. +func (in *KsApp) DeepCopy() *KsApp { + if in == nil { + return nil + } + out := new(KsApp) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *KsApp) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KsAppCondition) DeepCopyInto(out *KsAppCondition) { + *out = *in + in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KsAppCondition. +func (in *KsAppCondition) DeepCopy() *KsAppCondition { + if in == nil { + return nil + } + out := new(KsAppCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KsAppList) DeepCopyInto(out *KsAppList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]KsApp, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KsAppList. +func (in *KsAppList) DeepCopy() *KsAppList { + if in == nil { + return nil + } + out := new(KsAppList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *KsAppList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KsAppSpec) DeepCopyInto(out *KsAppSpec) { + *out = *in + if in.Components != nil { + in, out := &in.Components, &out.Components + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Packages != nil { + in, out := &in.Packages, &out.Packages + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Parameters != nil { + in, out := &in.Parameters, &out.Parameters + *out = make(map[string][]NameValue, len(*in)) + for key, val := range *in { + var outVal []NameValue + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make([]NameValue, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KsAppSpec. +func (in *KsAppSpec) DeepCopy() *KsAppSpec { + if in == nil { + return nil + } + out := new(KsAppSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KsAppStatus) DeepCopyInto(out *KsAppStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]KsAppCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KsAppStatus. +func (in *KsAppStatus) DeepCopy() *KsAppStatus { + if in == nil { + return nil + } + out := new(KsAppStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KsComponent) DeepCopyInto(out *KsComponent) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KsComponent. +func (in *KsComponent) DeepCopy() *KsComponent { + if in == nil { + return nil + } + out := new(KsComponent) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KsLibrary) DeepCopyInto(out *KsLibrary) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KsLibrary. +func (in *KsLibrary) DeepCopy() *KsLibrary { + if in == nil { + return nil + } + out := new(KsLibrary) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KsModule) DeepCopyInto(out *KsModule) { + *out = *in + if in.Components != nil { + in, out := &in.Components, &out.Components + *out = make([]*KsComponent, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(KsComponent) + **out = **in + } + } + } + if in.Modules != nil { + in, out := &in.Modules, &out.Modules + *out = make([]*KsModule, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(KsModule) + (*in).DeepCopyInto(*out) + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KsModule. +func (in *KsModule) DeepCopy() *KsModule { + if in == nil { + return nil + } + out := new(KsModule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KsPackage) DeepCopyInto(out *KsPackage) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KsPackage. +func (in *KsPackage) DeepCopy() *KsPackage { + if in == nil { + return nil + } + out := new(KsPackage) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KsParameter) DeepCopyInto(out *KsParameter) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KsParameter. +func (in *KsParameter) DeepCopy() *KsParameter { + if in == nil { + return nil + } + out := new(KsParameter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KsRegistry) DeepCopyInto(out *KsRegistry) { + *out = *in + if in.Libraries != nil { + in, out := &in.Libraries, &out.Libraries + *out = make(map[string]LibrarySpec, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KsRegistry. +func (in *KsRegistry) DeepCopy() *KsRegistry { + if in == nil { + return nil + } + out := new(KsRegistry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LibrarySpec) DeepCopyInto(out *LibrarySpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LibrarySpec. +func (in *LibrarySpec) DeepCopy() *LibrarySpec { + if in == nil { + return nil + } + out := new(LibrarySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NameValue) DeepCopyInto(out *NameValue) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NameValue. +func (in *NameValue) DeepCopy() *NameValue { + if in == nil { + return nil + } + out := new(NameValue) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RegistriesConfigFile) DeepCopyInto(out *RegistriesConfigFile) { + *out = *in + if in.Registries != nil { + in, out := &in.Registries, &out.Registries + *out = make([]*RegistryConfig, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(RegistryConfig) + **out = **in + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegistriesConfigFile. +func (in *RegistriesConfigFile) DeepCopy() *RegistriesConfigFile { + if in == nil { + return nil + } + out := new(RegistriesConfigFile) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Registry) DeepCopyInto(out *Registry) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Registry. +func (in *Registry) DeepCopy() *Registry { + if in == nil { + return nil + } + out := new(Registry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RegistryConfig) DeepCopyInto(out *RegistryConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegistryConfig. +func (in *RegistryConfig) DeepCopy() *RegistryConfig { + if in == nil { + return nil + } + out := new(RegistryConfig) + in.DeepCopyInto(out) + return out +} diff --git a/bootstrap/pkg/client/ksapp/ksapp.go b/bootstrap/pkg/client/ksapp/ksapp.go new file mode 100644 index 00000000000..b1e228abfb1 --- /dev/null +++ b/bootstrap/pkg/client/ksapp/ksapp.go @@ -0,0 +1,510 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ksapp + +import ( + "fmt" + "github.com/cenkalti/backoff" + "github.com/ghodss/yaml" + "github.com/ksonnet/ksonnet/pkg/actions" + "github.com/ksonnet/ksonnet/pkg/app" + "github.com/ksonnet/ksonnet/pkg/client" + "github.com/ksonnet/ksonnet/pkg/component" + kftypes "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps" + kstypes "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps/ksapp/v1alpha1" + log "github.com/sirupsen/logrus" + "github.com/spf13/afero" + "io/ioutil" + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "os" + "path" + "path/filepath" + "regexp" + "strings" + "time" +) + +// KsApp implements the KfApp Interface +type KsApp struct { + // AppDir is the directory where apps should be stored. + AppDir string + // ksonnet root name + KsName string + // ksonnet env name + KsEnvName string + KApp app.App + // kstypes.KsApp is autogenerated similar to k8 types + // It has a Spec member of type KsAppSpec that holds common fields like platform, version, repo + // components, packages, parameters. This information is used to generate a ksonnet app. + // It is not persisted into kfctl's app.yaml. + KsApp *kstypes.KsApp +} + +func GetKfApp(options map[string]interface{}) kftypes.KfApp { + _kfapp := &KsApp{ + KsName: kstypes.KsName, + KsEnvName: kstypes.KsEnvName, + KsApp: &kstypes.KsApp{ + TypeMeta: metav1.TypeMeta{ + Kind: "KsApp", + APIVersion: "ksapp.apps.kubeflow.org/v1alpha1", + }, + }, + } + _kfapp.KsApp.Spec.Platform = options[string(kftypes.PLATFORM)].(string) + if options[string(kftypes.APPNAME)] != nil { + _kfapp.KsApp.Name = options[string(kftypes.APPNAME)].(string) + } + if options[string(kftypes.APPDIR)] != nil { + _kfapp.AppDir = options[string(kftypes.APPDIR)].(string) + } + if options[string(kftypes.KAPP)] != nil { + _kfapp.KApp = options[string(kftypes.KAPP)].(app.App) + } + if options[string(kftypes.NAMESPACE)] != nil { + namespace := options[string(kftypes.NAMESPACE)].(string) + _kfapp.KsApp.Namespace = namespace + } + if options[string(kftypes.REPO)] != nil { + kubeflowRepo := options[string(kftypes.REPO)].(string) + re := regexp.MustCompile(`(^\$GOPATH)(.*$)`) + goPathVar := os.Getenv("GOPATH") + if goPathVar != "" { + kubeflowRepo = re.ReplaceAllString(kubeflowRepo, goPathVar+`$2`) + } + _kfapp.KsApp.Spec.Repo = kubeflowRepo + } + if options[string(kftypes.VERSION)] != nil { + kubeflowVersion := options[string(kftypes.VERSION)].(string) + _kfapp.KsApp.Spec.Version = kubeflowVersion + } + if options[string(kftypes.KSAPP)] != nil { + value := options[string(kftypes.KSAPP)] + ksApp := value.(*kstypes.KsApp) + _kfapp.KsApp = ksApp + } + return _kfapp +} + +func (ksApp *KsApp) writeConfigFile() error { + buf, bufErr := yaml.Marshal(ksApp.KsApp) + if bufErr != nil { + return bufErr + } + cfgFilePath := filepath.Join(ksApp.AppDir, kftypes.KfConfigFile) + cfgFilePathErr := ioutil.WriteFile(cfgFilePath, buf, 0644) + if cfgFilePathErr != nil { + return cfgFilePathErr + } + return nil +} + +func (ksApp *KsApp) Apply(resources kftypes.ResourceEnum, options map[string]interface{}) error { + host, _, err := kftypes.ServerVersion() + if err != nil { + return fmt.Errorf("couldn't get server version: %v", err) + } + cli, cliErr := kftypes.GetClientOutOfCluster() + if cliErr != nil { + return fmt.Errorf("couldn't create client Error: %v", cliErr) + } + envSetErr := ksApp.envSet(kstypes.KsEnvName, host) + if envSetErr != nil { + return fmt.Errorf("couldn't create ksonnet env %v Error: %v", kstypes.KsEnvName, envSetErr) + } + //ks param set application name ${DEPLOYMENT_NAME} + name := ksApp.KsApp.Name + paramSetErr := ksApp.paramSet("application", "name", name) + if paramSetErr != nil { + return fmt.Errorf("couldn't set application component's name to %v Error: %v", name, paramSetErr) + } + namespace := ksApp.KsApp.ObjectMeta.Namespace + log.Infof(string(kftypes.NAMESPACE)+": %v", namespace) + _, nsMissingErr := cli.CoreV1().Namespaces().Get(namespace, metav1.GetOptions{}) + if nsMissingErr != nil { + nsSpec := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} + _, nsErr := cli.CoreV1().Namespaces().Create(nsSpec) + if nsErr != nil { + return fmt.Errorf("couldn't create "+string(kftypes.NAMESPACE)+" %v Error: %v", namespace, nsErr) + } + } + clientConfig, clientConfigErr := kftypes.GetClientConfig() + if clientConfigErr != nil { + return fmt.Errorf("couldn't load client config Error: %v", clientConfigErr) + } + applyErr := ksApp.applyComponent([]string{"metacontroller"}, clientConfig) + if applyErr != nil { + return fmt.Errorf("couldn't create metacontroller component Error: %v", applyErr) + } + applyErr = ksApp.applyComponent([]string{"application"}, clientConfig) + if applyErr != nil { + return fmt.Errorf("couldn't create application component Error: %v", applyErr) + } + return nil +} + +func (ksApp *KsApp) applyComponent(components []string, cfg *clientcmdapi.Config) error { + applyOptions := map[string]interface{}{ + actions.OptionApp: ksApp.KApp, + actions.OptionClientConfig: &client.Config{ + Overrides: &clientcmd.ConfigOverrides{}, + Config: clientcmd.NewDefaultClientConfig(*cfg, &clientcmd.ConfigOverrides{}), + }, + actions.OptionComponentNames: components, + actions.OptionCreate: true, + actions.OptionDryRun: false, + actions.OptionEnvName: kstypes.KsEnvName, + actions.OptionGcTag: "gc-tag", + actions.OptionSkipGc: true, + } + bo := backoff.WithMaxRetries(backoff.NewConstantBackOff(5*time.Second), 6) + doneApply := make(map[string]bool) + err := backoff.Retry(func() error { + for _, comp := range components { + if _, ok := doneApply[comp]; ok { + continue + } + applyOptions[actions.OptionComponentNames] = []string{comp} + err := actions.RunApply(applyOptions) + if err == nil { + log.Infof("Component %v apply succeeded", comp) + doneApply[comp] = true + } else { + log.Errorf("(Will retry) Component %v apply failed; Error: %v", comp, err) + } + } + if len(doneApply) == len(components) { + return nil + } + return fmt.Errorf("%v failed components in last try", len(components)-len(doneApply)) + }, bo) + if err != nil { + log.Errorf("components apply failed; Error: %v", err) + } else { + log.Infof("All components apply succeeded") + } + return err + +} + +func (ksApp *KsApp) componentAdd(component kstypes.KsComponent, args []string) error { + componentPath := filepath.Join(ksApp.ksRoot(), "components", component.Name+".jsonnet") + componentArgs := make([]string, 0) + componentArgs = append(componentArgs, component.Prototype) + componentArgs = append(componentArgs, component.Name) + if args != nil && len(args) > 0 { + componentArgs = append(componentArgs, args[0:]...) + } + if exists, _ := afero.Exists(afero.NewOsFs(), componentPath); !exists { + log.Infof("Creating Component: %v ...", component.Name) + err := actions.RunPrototypeUse(map[string]interface{}{ + actions.OptionAppRoot: ksApp.ksRoot(), + actions.OptionArguments: componentArgs, + }) + if err != nil { + return fmt.Errorf("there was a problem adding component %v: %v", component.Name, err) + } + } else { + log.Infof("Component %v already exists", component.Name) + } + return nil +} + +func (ksApp *KsApp) components() (map[string]*kstypes.KsComponent, error) { + moduleName := "/" + topModule := component.NewModule(ksApp.KApp, moduleName) + components, err := topModule.Components() + if err != nil { + return nil, fmt.Errorf("there was a problem getting the components %v. Error: %v", ksApp.KsApp.Name, err) + } + comps := make(map[string]*kstypes.KsComponent) + for _, comp := range components { + name := comp.Name(false) + comps[name] = &kstypes.KsComponent{ + Name: name, + Prototype: name, + } + } + return comps, nil +} + +func (ksApp *KsApp) Delete(resources kftypes.ResourceEnum, options map[string]interface{}) error { + //TODO not deleting the following + //clusterrolebinding.rbac.authorization.k8s.io "meta-controller-cluster-role-binding" deleted + //customresourcedefinition.apiextensions.k8s.io "compositecontrollers.metacontroller.k8s.io" deleted + //customresourcedefinition.apiextensions.k8s.io "controllerrevisions.metacontroller.k8s.io" deleted + //customresourcedefinition.apiextensions.k8s.io "decoratorcontrollers.metacontroller.k8s.io" deleted + host, _, serverErr := kftypes.ServerVersion() + if serverErr != nil { + return fmt.Errorf("couldn't get server version: %v", serverErr) + } + cli, cliErr := kftypes.GetClientOutOfCluster() + if cliErr != nil { + return fmt.Errorf("couldn't create client Error: %v", cliErr) + } + envSetErr := ksApp.envSet(kstypes.KsEnvName, host) + if envSetErr != nil { + return fmt.Errorf("couldn't create ksonnet env %v Error: %v", kstypes.KsEnvName, envSetErr) + } + clientConfig, clientConfigErr := kftypes.GetClientConfig() + if clientConfigErr != nil { + return fmt.Errorf("couldn't load client config Error: %v", clientConfigErr) + } + components := []string{"application", "metacontroller"} + err := actions.RunDelete(map[string]interface{}{ + actions.OptionApp: ksApp.KApp, + actions.OptionClientConfig: &client.Config{ + Overrides: &clientcmd.ConfigOverrides{}, + Config: clientcmd.NewDefaultClientConfig(*clientConfig, &clientcmd.ConfigOverrides{}), + }, + actions.OptionEnvName: ksApp.KsEnvName, + actions.OptionComponentNames: components, + actions.OptionGracePeriod: int64(5), + }) + if err != nil { + log.Infof("there was a problem deleting %v: %v", components, err) + } + namespace := ksApp.KsApp.ObjectMeta.Namespace + log.Infof("deleting namespace: %v", namespace) + ns, nsMissingErr := cli.CoreV1().Namespaces().Get(namespace, metav1.GetOptions{}) + if nsMissingErr == nil { + nsErr := cli.CoreV1().Namespaces().Delete(ns.Name, metav1.NewDeleteOptions(int64(20))) + if nsErr != nil { + return fmt.Errorf("couldn't delete namespace %v Error: %v", namespace, nsErr) + } + } + return nil +} + +func (ksApp *KsApp) Generate(resources kftypes.ResourceEnum, options map[string]interface{}) error { + host, k8sSpec, err := kftypes.ServerVersion() + if err != nil { + return fmt.Errorf("couldn't get server version: %v", err) + } + pkgs := ksApp.KsApp.Spec.Packages + if pkgs == nil || len(pkgs) == 0 { + ksApp.KsApp.Spec.Packages = kstypes.DefaultPackages + } + comps := ksApp.KsApp.Spec.Components + if comps == nil || len(comps) == 0 { + ksApp.KsApp.Spec.Components = kstypes.DefaultComponents + } + parameters := ksApp.KsApp.Spec.Parameters + if parameters == nil || len(parameters) == 0 { + ksApp.KsApp.Spec.Parameters = kstypes.DefaultParameters + } + initErr := ksApp.initKs("default", k8sSpec, host, ksApp.KsApp.Namespace) + if initErr != nil { + return fmt.Errorf("couldn't initialize KfApi: %v", initErr) + } + ksRegistry := kstypes.DefaultRegistry + ksRegistry.Version = ksApp.KsApp.Spec.Version + ksRegistry.RegUri = ksApp.KsApp.Spec.Repo + registryAddErr := ksApp.registryAdd(ksRegistry) + if registryAddErr != nil { + return fmt.Errorf("couldn't add registry %v. Error: %v", ksRegistry.Name, registryAddErr) + } + packageArray := ksApp.KsApp.Spec.Packages + for _, pkgName := range packageArray { + pkg := kstypes.KsPackage{ + Name: pkgName, + Registry: "kubeflow", + } + packageAddErr := ksApp.pkgInstall(pkg) + if packageAddErr != nil { + return fmt.Errorf("couldn't add package %v. Error: %v", pkg.Name, packageAddErr) + } + } + componentArray := ksApp.KsApp.Spec.Components + for _, compName := range componentArray { + comp := kstypes.KsComponent{ + Name: compName, + Prototype: compName, + } + parameterMap := ksApp.KsApp.Spec.Parameters + parameterArgs := []string{} + parameters := parameterMap[compName] + if parameters != nil { + for _, parameter := range parameters { + name := "--" + parameter.Name + parameterArgs = append(parameterArgs, name) + value := parameter.Value + parameterArgs = append(parameterArgs, value) + } + } + if compName == "application" { + parameterArgs = append(parameterArgs, "--components") + prunedArray := kstypes.RemoveItems(componentArray, "application", "metacontroller") + quotedArray := kstypes.QuoteItems(prunedArray) + arrayString := "[" + strings.Join(quotedArray, ",") + "]" + parameterArgs = append(parameterArgs, arrayString) + } + componentAddErr := ksApp.componentAdd(comp, parameterArgs) + if componentAddErr != nil { + return fmt.Errorf("couldn't add comp %v. Error: %v", comp.Name, componentAddErr) + } + } + return nil +} + +func (ksApp *KsApp) Init(options map[string]interface{}) error { + log.Infof("KsApp.Init Name %v AppDir %v", ksApp.KsApp.Name, ksApp.AppDir) + err := os.Mkdir(ksApp.AppDir, os.ModePerm) + if err != nil { + return fmt.Errorf("couldn't create directory %v, most likely it already exists", ksApp.AppDir) + } + cfgFilePath := filepath.Join(ksApp.AppDir, kftypes.KfConfigFile) + _, appDirErr := afero.NewOsFs().Stat(cfgFilePath) + if appDirErr == nil { + return fmt.Errorf("config file %v already exists in %v", kftypes.KfConfigFile, ksApp.AppDir) + } + createConfigErr := ksApp.writeConfigFile() + if createConfigErr != nil { + return fmt.Errorf("cannot create config file app.yaml in %v", ksApp.AppDir) + } + return nil +} + +func (ksApp *KsApp) initKs(envName string, k8sSpecFlag string, host string, namespace string) error { + newRoot := path.Join(ksApp.AppDir, ksApp.KsName) + ksApp.KsEnvName = envName + options := map[string]interface{}{ + actions.OptionFs: afero.NewOsFs(), + actions.OptionName: ksApp.KsName, + actions.OptionEnvName: ksApp.KsEnvName, + actions.OptionNewRoot: newRoot, + actions.OptionServer: host, + actions.OptionSpecFlag: k8sSpecFlag, + actions.OptionNamespace: namespace, + actions.OptionSkipDefaultRegistries: true, + } + err := actions.RunInit(options) + if err != nil { + return fmt.Errorf("there was a problem initializing the app: %v", err) + } + log.Infof("Successfully initialized the app %v.", ksApp.KsApp.Name) + + return nil +} + +func (ksApp *KsApp) envSet(envName string, host string) error { + ksApp.KsEnvName = envName + err := actions.RunEnvSet(map[string]interface{}{ + actions.OptionAppRoot: ksApp.ksRoot(), + actions.OptionEnvName: ksApp.KsEnvName, + actions.OptionServer: host, + }) + if err != nil { + return fmt.Errorf("There was a problem setting ksonnet env: %v", err) + } + return nil +} + +func (ksApp *KsApp) ksRoot() string { + root := path.Join(ksApp.AppDir, ksApp.KsName) + return root +} + +func (ksApp *KsApp) libraries() (map[string]*kstypes.KsLibrary, error) { + libs, err := ksApp.KApp.Libraries() + if err != nil { + return nil, fmt.Errorf("there was a problem getting the libraries %v. Error: %v", ksApp.KsApp.Name, err) + } + + libraries := make(map[string]*kstypes.KsLibrary) + for k, v := range libs { + libraries[k] = &kstypes.KsLibrary{ + Name: v.Name, + Registry: v.Registry, + Version: v.Version, + } + } + return libraries, nil +} + +func (ksApp *KsApp) registries() (map[string]*kstypes.Registry, error) { + regs, err := ksApp.KApp.Registries() + if err != nil { + return nil, fmt.Errorf("There was a problem getting the registries %v. Error: %v", ksApp.KsApp.Name, err) + } + registries := make(map[string]*kstypes.Registry) + for k, v := range regs { + registries[k] = &kstypes.Registry{ + Name: v.Name, + Protocol: v.Protocol, + URI: v.URI, + } + } + + return registries, nil +} + +func (ksApp *KsApp) root() string { + return ksApp.AppDir +} + +func (ksApp *KsApp) paramSet(component string, name string, value string) error { + err := actions.RunParamSet(map[string]interface{}{ + actions.OptionAppRoot: ksApp.ksRoot(), + actions.OptionName: component, + actions.OptionPath: name, + actions.OptionValue: value, + }) + if err != nil { + return fmt.Errorf("Error when setting Parameters %v for Component %v: %v", name, component, err) + } + return nil +} + +func (ksApp *KsApp) pkgInstall(pkg kstypes.KsPackage) error { + root := ksApp.ksRoot() + err := actions.RunPkgInstall(map[string]interface{}{ + actions.OptionAppRoot: root, + actions.OptionPkgName: pkg.Registry + "/" + pkg.Name, + actions.OptionName: pkg.Name, + actions.OptionForce: false, + }) + if err != nil { + return fmt.Errorf("there was a problem installing package %v: %v", pkg.Name, err) + } + return nil +} + +func (ksApp *KsApp) prototypeUse(m map[string]interface{}) error { + return nil +} + +func (ksApp *KsApp) registryAdd(registry *kstypes.RegistryConfig) error { + log.Infof("App %v add registry %v URI %v", ksApp.KsApp.Name, registry.Name, registry.RegUri) + root := ksApp.ksRoot() + options := map[string]interface{}{ + actions.OptionAppRoot: root, + actions.OptionName: registry.Name, + actions.OptionURI: registry.RegUri, + actions.OptionPath: registry.Path, + actions.OptionVersion: registry.Version, + actions.OptionOverride: false, + } + err := actions.RunRegistryAdd(options) + if err != nil { + return fmt.Errorf("there was a problem adding registry %v: %v", registry.Name, err) + } + return nil +} diff --git a/bootstrap/pkg/client/minikube/minikube.go b/bootstrap/pkg/client/minikube/minikube.go new file mode 100644 index 00000000000..00748bbc0c6 --- /dev/null +++ b/bootstrap/pkg/client/minikube/minikube.go @@ -0,0 +1,150 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package minikube + +import ( + "fmt" + kftypes "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps" + kstypes "github.com/kubeflow/kubeflow/bootstrap/pkg/apis/apps/ksapp/v1alpha1" + "github.com/kubeflow/kubeflow/bootstrap/pkg/client/ksapp" + "os/user" + "strconv" + "strings" +) + +// MinikubeApp implements KfApp Interface +// It includes the KsApp along with functionality needed for minikube +type MinikubeApp struct { + ksApp kftypes.KfApp + //TODO add additional types required for minikube platform +} + +func GetKfApp(options map[string]interface{}) kftypes.KfApp { + _minikubeapp := &MinikubeApp{ + ksApp: ksapp.GetKfApp(options), + } + return _minikubeapp +} + +func (minikubeApp *MinikubeApp) writeConfigFile() error { + //TODO write out specific minikube parameters + return nil +} + +func (minikubeApp *MinikubeApp) Apply(resources kftypes.ResourceEnum, options map[string]interface{}) error { + ksApplyErr := minikubeApp.ksApp.Apply(resources, options) + if ksApplyErr != nil { + return fmt.Errorf("minikube apply failed for ksapp: %v", ksApplyErr) + } + //mount_local_fs + //setup_tunnels + return nil +} + +func (minikubeApp *MinikubeApp) Delete(resources kftypes.ResourceEnum, options map[string]interface{}) error { + ksDeleteErr := minikubeApp.ksApp.Delete(resources, options) + if ksDeleteErr != nil { + return fmt.Errorf("minikube delete failed for ksapp: %v", ksDeleteErr) + } + return nil +} + +func (minikubeApp *MinikubeApp) generateKsApp(options map[string]interface{}) error { + ksApp := minikubeApp.ksApp.(*ksapp.KsApp) + mountLocal := false + if options[string(kftypes.MOUNT_LOCAL)] != nil { + mountLocal = options[string(kftypes.MOUNT_LOCAL)].(bool) + } + // remove Katib package and component + pkgs := kstypes.RemoveItem(kstypes.DefaultPackages, "katib") + ksApp.KsApp.Spec.Packages = pkgs + comps := kstypes.RemoveItem(kstypes.DefaultComponents, "katib") + ksApp.KsApp.Spec.Components = comps + parameters := make(map[string][]kstypes.NameValue) + parameters["application"] = []kstypes.NameValue{ + { + Name: "components", + Value: "[" + strings.Join(kstypes.QuoteItems(comps), ",") + "]", + }, + } + usr, err := user.Current() + if err != nil { + return fmt.Errorf("Could not get current user; error %v", err) + } + uid := usr.Uid + gid := usr.Gid + parameters["jupyter"] = []kstypes.NameValue{ + { + Name: string(kftypes.PLATFORM), + Value: ksApp.KsApp.Spec.Platform, + }, + { + Name: "accessLocalFs", + Value: strconv.FormatBool(mountLocal), + }, + { + Name: "disks", + Value: "local-notebooks", + }, + { + Name: "notebookUid", + Value: uid, + }, + { + Name: "notebookGid", + Value: gid, + }, + } + parameters["ambassador"] = []kstypes.NameValue{ + { + Name: string(kftypes.PLATFORM), + Value: ksApp.KsApp.Spec.Platform, + }, + { + Name: "replicas", + Value: "1", + }, + } + ksApp.KsApp.Spec.Parameters = parameters + ksGenerateErr := minikubeApp.ksApp.Generate(kftypes.ALL, options) + if ksGenerateErr != nil { + return fmt.Errorf("minikube generate failed for ksapp: %v", ksGenerateErr) + } + return nil +} + +func (minikubeApp *MinikubeApp) Generate(resources kftypes.ResourceEnum, options map[string]interface{}) error { + switch resources { + case kftypes.ALL: + fallthrough + case kftypes.PLATFORM: + ksErr := minikubeApp.generateKsApp(options) + if ksErr != nil { + return fmt.Errorf("could not generate kssonnet under %v Error: %v", kstypes.KsName, ksErr) + } + case kftypes.K8S: + } + return nil +} + +func (minikubeApp *MinikubeApp) Init(options map[string]interface{}) error { + ksInitErr := minikubeApp.ksApp.Init(options) + if ksInitErr != nil { + return fmt.Errorf("minikube init failed for ksapp: %v", ksInitErr) + } + return nil +} diff --git a/bootstrap/pkg/utils/k8utils.go b/bootstrap/pkg/utils/k8utils.go new file mode 100644 index 00000000000..0ef603fba22 --- /dev/null +++ b/bootstrap/pkg/utils/k8utils.go @@ -0,0 +1,84 @@ +/* +Copyright (c) 2016-2017 Bitnami +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import ( + "errors" + "fmt" + "github.com/ghodss/yaml" + log "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "io/ioutil" + + "os" + "os/user" + "path" + + // Auth plugins + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" +) + +// RecommendedConfigPathEnvVar is a environment variable for path configuration +const RecommendedConfigPathEnvVar = "KUBECONFIG" + +// GetKubeConfigFile tries to find a kubeconfig file. +func GetKubeConfigFile() string { + configFile := "" + usr, err := user.Current() + if err != nil { + log.Warningf("Could not get current user; error %v", err) + } else { + configFile = path.Join(usr.HomeDir, ".kube", "config") + } + if len(os.Getenv(RecommendedConfigPathEnvVar)) > 0 { + configFile = os.Getenv(RecommendedConfigPathEnvVar) + } + return configFile +} + +func GetApiServer() (string, error) { + kubeconfig := viper.New() + path := GetKubeConfigFile() + kubeconfig.SetConfigType("yaml") + kubeconfig.SetConfigFile(path) + configErr := kubeconfig.ReadInConfig() + if configErr != nil { + return "", fmt.Errorf("could not read in %v. Error: %v", path, configErr) + } + currentContext := kubeconfig.GetString("current-context") + log.Infof("current-context is %v", currentContext) + + clusters := kubeconfig.GetStringMap("clusters") + for cluster := range clusters { + log.Infof("cluster is %v", cluster) + + } + return "", nil +} + +// Load yaml config +func LoadConfigFile(path string, o interface{}) error { + if path == "" { + return errors.New("empty path") + } + data, err := ioutil.ReadFile(path) + if err != nil { + return err + } + if err = yaml.Unmarshal(data, o); err != nil { + return err + } + return nil +} diff --git a/kubeflow/jupyter/jupyter.libsonnet b/kubeflow/jupyter/jupyter.libsonnet index 49cf86fdd88..8316ff3daf1 100644 --- a/kubeflow/jupyter/jupyter.libsonnet +++ b/kubeflow/jupyter/jupyter.libsonnet @@ -394,6 +394,10 @@ }, notebookRoleBinding:: notebookRoleBinding, + local localstorage = (import "localstorage.libsonnet"), + pv:: localstorage.pv, + pvclaim:: localstorage.pvclaim, + parts:: self, all:: [ self.kubeSpawnerConfig, @@ -406,7 +410,12 @@ self.notebookServiceAccount, self.hubRoleBinding, self.notebookRoleBinding, - ], + ] + std.flattenArrays([ + if params.accessLocalFs == "true" then [ + self.pv, + self.pvclaim, + ] else [], + ]), list(obj=self.all):: util.list(obj), }, diff --git a/kubeflow/jupyter/localstorage.libsonnet b/kubeflow/jupyter/localstorage.libsonnet new file mode 100644 index 00000000000..b0b56b7b006 --- /dev/null +++ b/kubeflow/jupyter/localstorage.libsonnet @@ -0,0 +1,64 @@ +{ + local pv = { + kind: 'PersistentVolume', + apiVersion: 'v1', + metadata: { + name: 'local-volume', + labels: { + type: 'local', + }, + }, + spec: { + persistentVolumeReclaimPolicy: 'Delete', + storageClassName: 'local-storage', + capacity: { + storage: '10Gi', + }, + accessModes: [ + 'ReadWriteOnce', + ], + 'local': { + path: '/mnt/local', + }, + nodeAffinity: { + required: { + nodeSelectorTerms: [ + { + matchExpressions: [ + { + key: 'kubernetes.io/hostname', + operator: 'In', + values: [ + 'minikube', + ], + }, + ], + }, + ], + }, + }, + }, + }, + pv:: pv, + + local pvclaim = { + kind: 'PersistentVolumeClaim', + apiVersion: 'v1', + metadata: { + name: 'local-notebooks', + }, + spec: { + storageClassName: 'local-storage', + accessModes: [ + 'ReadWriteOnce', + ], + resources: { + requests: { + storage: '10Gi', + }, + }, + volumeName: 'local-volume', + }, + }, + pvclaim:: pvclaim, +} diff --git a/kubeflow/jupyter/prototypes/jupyter.jsonnet b/kubeflow/jupyter/prototypes/jupyter.jsonnet index 4393576889d..1b212778a60 100644 --- a/kubeflow/jupyter/prototypes/jupyter.jsonnet +++ b/kubeflow/jupyter/prototypes/jupyter.jsonnet @@ -9,6 +9,7 @@ // @optionalParam jupyterHubAuthenticator string null The authenticator to use // @optionalParam useJupyterLabAsDefault string false Set JupterLab interface as the default // @optionalParam gcpSecretName string user-gcp-sa The name of the secret containing service account credentials for GCP +// @optionalParam disks string null Comma separated list of Google persistent disks to attach to notebook environments. // @optionalParam notebookUid string -1 UserId of the host user for minikube local fs mount // @optionalParam notebookGid string -1 GroupID of the host user for minikube local fs mount // @optionalParam accessLocalFs string false Set true if mounting a local fs directory that needs to be accessed by Jupyter Notebook in Minikube. diff --git a/kubeflow/jupyter/tests/jupyter_test.jsonnet b/kubeflow/jupyter/tests/jupyter_test.jsonnet index 4da3aa27852..37ec24c4430 100644 --- a/kubeflow/jupyter/tests/jupyter_test.jsonnet +++ b/kubeflow/jupyter/tests/jupyter_test.jsonnet @@ -360,6 +360,73 @@ local testCases = [ ], }, }, + { + actual: instance.parts.pv, + expected: + { + apiVersion: "v1", + kind: "PersistentVolume", + metadata: { + labels: { + type: "local", + }, + name: "local-volume", + }, + spec: { + accessModes: [ + "ReadWriteOnce", + ], + capacity: { + storage: "10Gi", + }, + "local": { + path: "/mnt/local", + }, + nodeAffinity: { + required: { + nodeSelectorTerms: [ + { + matchExpressions: [ + { + key: "kubernetes.io/hostname", + operator: "In", + values: [ + "minikube", + ], + }, + ], + }, + ], + }, + }, + persistentVolumeReclaimPolicy: "Delete", + storageClassName: "local-storage", + }, + }, + }, + { + actual: instance.parts.pvclaim, + expected: + { + apiVersion: "v1", + kind: "PersistentVolumeClaim", + metadata: { + name: "local-notebooks", + }, + spec: { + accessModes: [ + "ReadWriteOnce", + ], + resources: { + requests: { + storage: "10Gi", + }, + }, + storageClassName: "local-storage", + volumeName: "local-volume", + }, + }, + }, ]; testSuite.run(testCases) diff --git a/kubeflow/jupyter/tests/notebooks_test.jsonnet b/kubeflow/jupyter/tests/notebooks_test.jsonnet index 2f5d0aaa356..e512b992522 100644 --- a/kubeflow/jupyter/tests/notebooks_test.jsonnet +++ b/kubeflow/jupyter/tests/notebooks_test.jsonnet @@ -12,7 +12,7 @@ local params = { repoName: "kubeflow-images-public", notebookUid: "-1", notebookGid: "-1", - accessLocalFs: "false", + accessLocalFs: "true", }; local env = { @@ -133,48 +133,49 @@ local testCases = [ }, { actual: instance.parts.notebooksController, - expected: { - apiVersion: "metacontroller.k8s.io/v1alpha1", - kind: "CompositeController", - metadata: { - annotations: { - accessLocalFs: "false", - image: "gcr.io/kubeflow/jupyterhub-k8s:v20180531-3bb991b1", - namespace: "kf-100", - notebookGid: "-1", - notebookPVCMount: "/home/jovyan", - notebookUid: "-1", - registry: "gcr.io", - repoName: "kubeflow-images-public", - useJupyterLabAsDefault: true, - }, - name: "notebook-controller", - }, - spec: { - childResources: [ - { - apiVersion: "v1", - resource: "services", - }, - { - apiVersion: "extensions/v1beta1", - resource: "deployments", + expected: + { + apiVersion: "metacontroller.k8s.io/v1alpha1", + kind: "CompositeController", + metadata: { + annotations: { + accessLocalFs: "true", + image: "gcr.io/kubeflow/jupyterhub-k8s:v20180531-3bb991b1", + namespace: "kf-100", + notebookGid: "-1", + notebookPVCMount: "/home/jovyan", + notebookUid: "-1", + registry: "gcr.io", + repoName: "kubeflow-images-public", + useJupyterLabAsDefault: true, }, - ], - generateSelector: true, - hooks: { - sync: { - webhook: { - url: "http://notebooks.kf-100/sync-notebook", + name: "notebook-controller", + }, + spec: { + childResources: [ + { + apiVersion: "v1", + resource: "services", + }, + { + apiVersion: "extensions/v1beta1", + resource: "deployments", + }, + ], + generateSelector: true, + hooks: { + sync: { + webhook: { + url: "http://notebooks.kf-100/sync-notebook", + }, }, }, - }, - parentResource: { - apiVersion: "kubeflow.org/v1alpha1", - resource: "notebooks", + parentResource: { + apiVersion: "kubeflow.org/v1alpha1", + resource: "notebooks", + }, }, }, - }, }, ];