diff --git a/Makefile b/Makefile index f5bc229ae..245fddcd7 100755 --- a/Makefile +++ b/Makefile @@ -103,6 +103,10 @@ pkg/kfconfig/awsplugin/zz_generated.deepcopy.go: pkg/kfconfig/awsplugin/types.go ${GOPATH}/bin/deepcopy-gen -i github.com/kubeflow/kfctl/v3/pkg/kfconfig/awsplugin/... -O zz_generated.deepcopy && \ mv ${GOPATH}/src/github.com/kubeflow/kfctl/v3/pkg/kfconfig/awsplugin/zz_generated.deepcopy.go pkg/kfconfig/awsplugin/ && rm -rf v3 +pkg/kfconfig/dexplugin/zz_generated.deepcopy.go: pkg/kfconfig/dexplugin/types.go + ${GOPATH}/bin/deepcopy-gen -i github.com/kubeflow/kfctl/v3/pkg/kfconfig/dexplugin/... -O zz_generated.deepcopy && \ + mv ${GOPATH}/src/github.com/kubeflow/kfctl/v3/pkg/kfconfig/dexplugin/zz_generated.deepcopy.go pkg/kfconfig/dexplugin/ && rm -rf v3 + pkg/kfconfig/gcpplugin/zz_generated.deepcopy.go: pkg/kfconfig/gcpplugin/types.go ${GOPATH}/bin/deepcopy-gen -i github.com/kubeflow/kfctl/v3/pkg/kfconfig/gcpplugin/... -O zz_generated.deepcopy && \ mv ${GOPATH}/src/github.com/kubeflow/kfctl/v3/pkg/kfconfig/gcpplugin/zz_generated.deepcopy.go pkg/kfconfig/gcpplugin/ && rm -rf v3 @@ -114,6 +118,7 @@ deepcopy: ${GOPATH}/bin/deepcopy-gen config/zz_generated.deepcopy.go \ pkg/apis/apps/plugins/gcp/v1alpha1/zz_generated.deepcopy.go \ pkg/apis/apps/plugins/aws/v1alpha1/zz_generated.deepcopy.go \ pkg/kfconfig/zz_generated.deepcopy.go \ + pkg/kfconfig/dexplugin/zz_generated.deepcopy.go \ pkg/kfconfig/awsplugin/zz_generated.deepcopy.go \ pkg/kfconfig/gcpplugin/zz_generated.deepcopy.go diff --git a/go.mod b/go.mod index d04e7f99f..fe35cf132 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,11 @@ require ( github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/Sirupsen/logrus v0.0.0-00010101000000-000000000000 // indirect + github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf github.com/aws/aws-sdk-go v1.16.26 github.com/cenkalti/backoff v2.2.1+incompatible github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect + github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d // indirect github.com/deckarep/golang-set v1.7.1 github.com/docker/docker v1.13.1 // indirect github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect @@ -23,9 +25,11 @@ require ( github.com/gogo/protobuf v1.2.1 github.com/google/go-cmp v0.3.0 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.5.0 // indirect github.com/hashicorp/go-getter v1.0.2 github.com/imdario/mergo v0.3.7 github.com/jonboulle/clockwork v0.1.0 // indirect + github.com/kr/pty v1.1.3 // indirect github.com/kubeflow/kubeflow/components/profile-controller v0.0.0-20190614045418-7ca3cfb39368 // indirect github.com/kubernetes-sigs/application v0.8.0 github.com/mitchellh/go-wordwrap v1.0.0 // indirect @@ -34,6 +38,8 @@ require ( github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776 // indirect github.com/pkg/errors v0.8.1 github.com/prometheus/common v0.2.0 + github.com/russross/blackfriday v1.5.2 // indirect + github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect github.com/sirupsen/logrus v1.4.2 github.com/spf13/cobra v0.0.3 github.com/spf13/viper v1.3.1 diff --git a/go.sum b/go.sum index 4bc17e11b..67af6f596 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,7 @@ github.com/appscode/jsonpatch v0.0.0-20190108182946-7c0e3b262f30/go.mod h1:4AJxU github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= diff --git a/pkg/apis/apps/group.go b/pkg/apis/apps/group.go index 95c2196b5..2db86e5f8 100644 --- a/pkg/apis/apps/group.go +++ b/pkg/apis/apps/group.go @@ -58,8 +58,10 @@ const ( DefaultAppLabel = "app.kubernetes.io/name" DefaultAppVersion = "app.kubernetes.io/version" DefaultAppType = "kubeflow" - KUBEFLOW_USERNAME = "KUBEFLOW_USERNAME" + KubeflowDomain = "KUBEFLOW_DOMAIN" + KubeflowEmail = "KUBEFLOW_EMAIL" KUBEFLOW_PASSWORD = "KUBEFLOW_PASSWORD" + KUBEFLOW_USERNAME = "KUBEFLOW_USERNAME" DefaultSwaggerFile = "bootstrap/k8sSpec/v1.11.7/api/openapi-spec/swagger.json" YamlSeparator = "(?m)^---[ \t]*$" Dns1123LabelFmt = "[a-z0-9]([-a-z0-9]*[a-z0-9])?" @@ -156,10 +158,10 @@ func RemoveItem(defaults []string, name string) []string { // Platforms const ( - AWS = "aws" - GCP = "gcp" - MINIKUBE = "minikube" - EXISTING_ARRIKTO = "existing_arrikto" + AWS = "aws" + GCP = "gcp" + MINIKUBE = "minikube" + DEX = "dex" ) // PackageManagers diff --git a/pkg/apis/apps/kfconfig/types.go b/pkg/apis/apps/kfconfig/types.go index 9310be859..72a0d13c4 100644 --- a/pkg/apis/apps/kfconfig/types.go +++ b/pkg/apis/apps/kfconfig/types.go @@ -176,10 +176,10 @@ const ( // Plugin kind used starting from v1beta1 const ( - AWS_PLUGIN_KIND PluginKindType = "KfAwsPlugin" - GCP_PLUGIN_KIND PluginKindType = "KfGcpPlugin" - MINIKUBE_PLUGIN_KIND PluginKindType = "KfMinikubePlugin" - EXISTING_ARRIKTO_PLUGIN_KIND PluginKindType = "KfExistingArriktoPlugin" + AWS_PLUGIN_KIND PluginKindType = "KfAwsPlugin" + DEX_PLUGIN_KIND PluginKindType = "KfDexPlugin" + GCP_PLUGIN_KIND PluginKindType = "KfGcpPlugin" + MINIKUBE_PLUGIN_KIND PluginKindType = "KfMinikubePlugin" ) type ConditionType string diff --git a/pkg/apis/apps/plugins/gcp/v1alpha1/types.go b/pkg/apis/apps/plugins/gcp/v1alpha1/types.go index b46a9bc59..99b75a867 100644 --- a/pkg/apis/apps/plugins/gcp/v1alpha1/types.go +++ b/pkg/apis/apps/plugins/gcp/v1alpha1/types.go @@ -2,6 +2,7 @@ package v1alpha1 import ( "fmt" + kfdeftypes "github.com/kubeflow/kfctl/v3/pkg/apis/apps/kfdef/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) diff --git a/pkg/kfapp/coordinator/coordinator.go b/pkg/kfapp/coordinator/coordinator.go index 7087ddd3c..15ebb30ab 100644 --- a/pkg/kfapp/coordinator/coordinator.go +++ b/pkg/kfapp/coordinator/coordinator.go @@ -28,7 +28,7 @@ import ( kfdefsv1alpha1 "github.com/kubeflow/kfctl/v3/pkg/apis/apps/kfdef/v1alpha1" kfdefsv1beta1 "github.com/kubeflow/kfctl/v3/pkg/apis/apps/kfdef/v1beta1" "github.com/kubeflow/kfctl/v3/pkg/kfapp/aws" - "github.com/kubeflow/kfctl/v3/pkg/kfapp/existing_arrikto" + "github.com/kubeflow/kfctl/v3/pkg/kfapp/dex" "github.com/kubeflow/kfctl/v3/pkg/kfapp/gcp" "github.com/kubeflow/kfctl/v3/pkg/kfapp/kustomize" "github.com/kubeflow/kfctl/v3/pkg/kfapp/minikube" @@ -59,8 +59,8 @@ func getPlatform(kfdef *kfconfig.KfConfig) (kftypesv3.Platform, error) { return minikube.Getplatform(kfdef), nil case string(kftypesv3.GCP): return gcp.GetPlatform(kfdef) - case string(kftypesv3.EXISTING_ARRIKTO): - return existing_arrikto.GetPlatform(kfdef) + case string(kftypesv3.DEX): + return dex.GetPlatform(kfdef) case string(kftypesv3.AWS): return aws.GetPlatform(kfdef) default: diff --git a/pkg/kfapp/dex/dex.go b/pkg/kfapp/dex/dex.go new file mode 100644 index 000000000..f350922af --- /dev/null +++ b/pkg/kfapp/dex/dex.go @@ -0,0 +1,449 @@ +// Copyright 2019 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 dex + +import ( + "context" + "encoding/base64" + "fmt" + "os" + + "github.com/asaskevich/govalidator" + "github.com/pkg/errors" + "golang.org/x/crypto/bcrypt" + + kfapis "github.com/kubeflow/kfctl/v3/pkg/apis" + kftypes "github.com/kubeflow/kfctl/v3/pkg/apis/apps" + "github.com/kubeflow/kfctl/v3/pkg/kfconfig" + "github.com/kubeflow/kfctl/v3/pkg/kfconfig/dexplugin" + log "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +const ( + staticPasswordAuthSecret = "kubeflow-login" + kubeflowDomain = "kubeflow-domain" + + // DexPluginName Plugin parameter constants + DexPluginName = kfconfig.DEX_PLUGIN_KIND + StaticPasswordAuthPasswordSecretName = "password" +) + +// Dex implements KfApp Interface +type Dex struct { + kfDef *kfconfig.KfConfig +} + +// GetPlatform returns the dex kfapp. It's called by coordinator.GetKfApp +func GetPlatform(kfdef *kfconfig.KfConfig) (kftypes.Platform, error) { + _dex := &Dex{ + kfDef: kfdef, + } + + return _dex, nil +} + +// GetPluginSpec gets the plugin spec. +func (dex *Dex) GetPluginSpec() (*dexplugin.DexPluginSpec, error) { + dexPluginSpec := &dexplugin.DexPluginSpec{} + + err := dex.kfDef.GetPluginSpec(DexPluginName, dexPluginSpec) + + return dexPluginSpec, err +} + +// GetK8sConfig is only used with ksonnet packageManager. NotImplemented in this version, return nil to use default config for API compatibility. +func (dex *Dex) GetK8sConfig() (*rest.Config, *clientcmdapi.Config) { + return nil, nil +} + +func insertConfigMap(client *clientset.Clientset, configMapName string, namespace string, data map[string]string) error { + configMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: configMapName, + Namespace: namespace, + }, + Data: data, + } + _, err := client.CoreV1().ConfigMaps(namespace).Create(configMap) + if err == nil { + return nil + } else { + return &kfapis.KfError{ + Code: int(kfapis.INTERNAL_ERROR), + Message: err.Error(), + } + } +} + +func insertSecret(client *clientset.Clientset, secretName string, namespace string, data map[string][]byte) error { + secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + }, + Data: data, + } + _, err := client.CoreV1().Secrets(namespace).Create(secret) + if err == nil { + return nil + } else { + return &kfapis.KfError{ + Code: int(kfapis.INTERNAL_ERROR), + Message: err.Error(), + } + } +} + +func (dex *Dex) getAuthNamespace() string { + if authNamespace, ok := dex.kfDef.GetApplicationParameter("dex", "namespace"); ok { + return authNamespace + } + return dex.kfDef.Namespace +} + +func (dex *Dex) getIstioNamespace() string { + if istioNamespace, ok := dex.kfDef.GetApplicationParameter("oidc-authservice", "namespace"); ok { + return istioNamespace + } + return dex.kfDef.Namespace +} + +// createDomainConfigMap creates a configMap containing the domain on which +// Kubeflow's dashboard will be served. TLS cert is issued on this Domain. +func (dex *Dex) createDomainConfigMap(client *clientset.Clientset) error { + dexPluginSpec, err := dex.GetPluginSpec() + if err != nil { + return err + } + + ctx := context.Background() + k8sClientset, err := dex.getK8sClientset(ctx) + if err != nil { + return err + } + + istioNamespace := dex.getIstioNamespace() + if err = createNamespace(k8sClientset, istioNamespace); err != nil { + return err + } + + configMapData := map[string]string{ + "dns_name": "", + "ip": "", + } + if govalidator.IsDNSName(dexPluginSpec.Domain) { + configMapData["dns_name"] = dexPluginSpec.Domain + } else if govalidator.IsIP(dexPluginSpec.Domain) { + configMapData["ip"] = dexPluginSpec.Domain + } + configMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: kubeflowDomain, + Namespace: istioNamespace, + }, + Data: configMapData, + } + _, err = client.CoreV1().ConfigMaps(istioNamespace).Update(configMap) + if err != nil { + log.Warnf("Updating configMap for Dex domain failed, trying to create one: %v", err) + return insertConfigMap(client, kubeflowDomain, istioNamespace, configMapData) + } + return nil +} + +// Use email and password provided by user and create secret for staticPassword auth. +func (dex *Dex) createStaticUserAuthSecret(client *clientset.Clientset) error { + ctx := context.Background() + k8sClientset, err := dex.getK8sClientset(ctx) + if err != nil { + return err + } + + dexPluginSpec, err := dex.GetPluginSpec() + if err != nil { + return err + } + + if dexPluginSpec.Auth == nil || dexPluginSpec.Auth.StaticPasswordAuth == nil || dexPluginSpec.Auth.StaticPasswordAuth.Password.Name == "" { + err := errors.WithStack(fmt.Errorf("StaticPasswordAuth.Password.Name must be set")) + return err + } + + password, err := dex.kfDef.GetSecret(dexPluginSpec.Auth.StaticPasswordAuth.Password.Name) + if err != nil { + log.Errorf("There was a problem getting the password for basic auth; error %v", err) + return err + } + + encodedPassword, err := base64EncryptPassword(password) + if err != nil { + log.Errorf("There was a problem encrypting the password; %v", err) + return err + } + + authNamespace := dex.getAuthNamespace() + if err = createNamespace(k8sClientset, authNamespace); err != nil { + return err + } + + secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: staticPasswordAuthSecret, + Namespace: authNamespace, + }, + Data: map[string][]byte{ + "email": []byte(dexPluginSpec.Auth.StaticPasswordAuth.Email), + "passwordhash": []byte(encodedPassword), + }, + } + _, err = client.CoreV1().Secrets(authNamespace).Update(secret) + if err != nil { + log.Warnf("Updating static user auth login failed, trying to create one: %v", err) + return insertSecret(client, staticPasswordAuthSecret, authNamespace, map[string][]byte{ + "email": []byte(dexPluginSpec.Auth.StaticPasswordAuth.Email), + "passwordhash": []byte(encodedPassword), + }) + } + return nil +} + +func base64EncryptPassword(password string) (string, error) { + passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), 10) + if err != nil { + return "", &kfapis.KfError{ + Code: int(kfapis.INVALID_ARGUMENT), + Message: fmt.Sprintf("Error when hashing password: %v", err), + } + } + encodedPassword := base64.StdEncoding.EncodeToString(passwordHash) + + return encodedPassword, nil +} + +// Init initializes dex kfapp - platform +func (dex *Dex) Init(resources kftypes.ResourceEnum) error { + + return nil +} + +// Generate generates dex kfapp manifest +func (dex *Dex) Generate(resources kftypes.ResourceEnum) error { + + if setDexPluginDefaultsErr := dex.setDexPluginDefaults(); setDexPluginDefaultsErr != nil { + return &kfapis.KfError{ + Code: setDexPluginDefaultsErr.(*kfapis.KfError).Code, + Message: fmt.Sprintf("set dex plugin defaults Error %v", + setDexPluginDefaultsErr.(*kfapis.KfError).Message), + } + } + + dexPluginSpec, err := dex.GetPluginSpec() + if err != nil { + return err + } + + if dexPluginSpec.Auth.UseStaticPassword { + if err := dex.kfDef.SetApplicationParameter( + "dex", + "static_email", + dexPluginSpec.Auth.StaticPasswordAuth.Email, + ); err != nil { + return errors.WithStack(err) + } + } + + return nil +} + +func (dex *Dex) setDexPluginDefaults() error { + dexPluginSpec, err := dex.GetPluginSpec() + + if err != nil { + return err + } + + if dexPluginSpec.Auth.UseStaticPassword { + log.Infof("Using static password for Dex") + if dexPluginSpec.Auth.StaticPasswordAuth == nil { + dexPluginSpec.Auth.StaticPasswordAuth = &dexplugin.StaticPasswordAuth{} + } + + domain := os.Getenv(kftypes.KubeflowDomain) + if domain == "" { + log.Warnf("KUBEFLOW_DOMAIN isn't set. TLS will be set on Istio Ingress Gateway IP.") + } else if !(govalidator.IsDNSName(domain) || govalidator.IsIP(domain)) { + return &kfapis.KfError{ + Code: int(kfapis.INVALID_ARGUMENT), + Message: fmt.Sprintf( + "KUBEFLOW_DOMAIN: %s is an invalid domain.", + domain, + ), + } + } + dexPluginSpec.Domain = domain + + email := os.Getenv(kftypes.KubeflowEmail) + if email == "" { + log.Errorf("Could not configure static user auth; environment variable %s not set", kftypes.KubeflowEmail) + return &kfapis.KfError{ + Code: int(kfapis.INVALID_ARGUMENT), + Message: fmt.Sprintf( + "Could not configure static user auth; environment variable %s not set", + kftypes.KubeflowEmail, + ), + } + } + dexPluginSpec.Auth.StaticPasswordAuth.Email = email + + dexPluginSpec.Auth.StaticPasswordAuth.Password = &kfconfig.SecretRef{ + Name: StaticPasswordAuthPasswordSecretName, + } + password := os.Getenv(kftypes.KUBEFLOW_PASSWORD) + if password == "" { + log.Errorf("Could not configure static user auth; environment variable %s not set", kftypes.KUBEFLOW_PASSWORD) + return &kfapis.KfError{ + Code: int(kfapis.INVALID_ARGUMENT), + Message: fmt.Sprintf("Could not configure basic auth; environment variable %s not set", kftypes.KUBEFLOW_PASSWORD), + } + } + + dex.kfDef.SetSecret(kfconfig.Secret{ + Name: StaticPasswordAuthPasswordSecretName, + SecretSource: &kfconfig.SecretSource{ + EnvSource: &kfconfig.EnvSource{ + Name: kftypes.KUBEFLOW_PASSWORD, + }, + }, + }) + } + + if err := dex.kfDef.SetPluginSpec(DexPluginName, dexPluginSpec); err != nil { + return errors.WithStack(err) + } + + return nil +} + +// Apply applys kfdef manifests for dex +func (dex *Dex) Apply(resources kftypes.ResourceEnum) error { + + // Inserts configMaps into the cluster + configMapsErr := dex.createConfigMaps() + if configMapsErr != nil { + return &kfapis.KfError{ + Code: configMapsErr.(*kfapis.KfError).Code, + Message: fmt.Sprintf("dex apply could not create configMaps Error %v", + configMapsErr.(*kfapis.KfError).Message), + } + } + + // Inserts secrets into the cluster + secretsErr := dex.createSecrets() + if secretsErr != nil { + return &kfapis.KfError{ + Code: secretsErr.(*kfapis.KfError).Code, + Message: fmt.Sprintf("dex apply could not create secrets Error %v", + secretsErr.(*kfapis.KfError).Message), + } + } + + // TODO(krishnadurai): Figure how to set secrets in config.yaml for Dex? + + return nil +} + +func (dex *Dex) Delete(resources kftypes.ResourceEnum) error { + + return nil +} + +func (dex *Dex) createConfigMaps() error { + ctx := context.Background() + + k8sClient, err := dex.getK8sClientset(ctx) + if err != nil { + return kfapis.NewKfErrorWithMessage(err, "set K8s clientset error") + } + log.Infof("Creating Dex configMap for kubeflow domain ...") + if err := dex.createDomainConfigMap(k8sClient); err != nil { + return kfapis.NewKfErrorWithMessage(err, "cannot create dex configMap for kubflow domain") + } + return nil +} + +func (dex *Dex) createSecrets() error { + ctx := context.Background() + + dexPluginSpec, err := dex.GetPluginSpec() + if err != nil { + return err + } + + k8sClient, err := dex.getK8sClientset(ctx) + if err != nil { + return kfapis.NewKfErrorWithMessage(err, "set K8s clientset error") + } + log.Infof("Creating Dex secrets...") + if dexPluginSpec.Auth.UseStaticPassword { + log.Infof("Creating Dex secrets for staticPassword auth...") + if err := dex.createStaticUserAuthSecret(k8sClient); err != nil { + return kfapis.NewKfErrorWithMessage(err, "cannot create dex auth login secret") + } + } + return nil +} + +func createNamespace(k8sClientset *clientset.Clientset, namespace string) error { + + log.Infof("Creating namespace: %v", namespace) + _, err := k8sClientset.CoreV1().Namespaces().Get(namespace, metav1.GetOptions{}) + if err == nil { + log.Infof("Namespace already exists...") + return nil + } + log.Infof("Get namespace error: %v", err) + _, err = k8sClientset.CoreV1().Namespaces().Create( + &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }, + ) + if err == nil { + return nil + } else { + return &kfapis.KfError{ + Code: int(kfapis.INTERNAL_ERROR), + Message: err.Error(), + } + } +} + +func (dex *Dex) getK8sClientset(ctx context.Context) (*clientset.Clientset, error) { + config := kftypes.GetConfig() + if cli, err := clientset.NewForConfig(config); err == nil { + return cli, nil + } else { + return nil, &kfapis.KfError{ + Code: int(kfapis.INTERNAL_ERROR), + Message: fmt.Sprintf("create new ClientConfig error: %v", err), + } + } +} diff --git a/pkg/kfconfig/dexplugin/doc.go b/pkg/kfconfig/dexplugin/doc.go new file mode 100644 index 000000000..adb4f160d --- /dev/null +++ b/pkg/kfconfig/dexplugin/doc.go @@ -0,0 +1,22 @@ +// Copyright 2019 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 v1beta1 contains API Schema definitions for the kfdef v1beta1 API group +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package,register +// +k8s:conversion-gen=github.com/kubeflow/kfctl/v3/pkg/kfconfig/dexplugin +// +k8s:defaulter-gen=TypeMeta +// +groupName=dexplugin.internal.kubeflow.org + +package dexplugin diff --git a/pkg/kfconfig/dexplugin/register.go b/pkg/kfconfig/dexplugin/register.go new file mode 100644 index 000000000..437669667 --- /dev/null +++ b/pkg/kfconfig/dexplugin/register.go @@ -0,0 +1,61 @@ +// Copyright 2019 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 KfAwsPlugin v1alpha1. +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package,register +// +k8s:conversion-gen=github.com/kubeflow/kfctl/v3/pkg/kfconfig/dexplugin +// +k8s:defaulter-gen=TypeMeta +// +groupName=dexplugin.internal.kubeflow.org +package dexplugin + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/kubernetes/scheme" +) + +var ( + // SchemeGroupVersion is group version used to register these objects + SchemeGroupVersion = schema.GroupVersion{Group: "dexplugin.internal.kubeflow.org", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + localSchemeBuilder = &SchemeBuilder + + // AddToScheme is required by pkg/kfdef/... + AddToScheme = localSchemeBuilder.AddToScheme +) + +// Resource is required by pkg/kfdef/listers/... +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &KfDexPlugin{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} + +func init() { + metav1.AddToGroupVersion(scheme.Scheme, SchemeGroupVersion) + utilruntime.Must(AddToScheme(scheme.Scheme)) +} diff --git a/pkg/kfconfig/dexplugin/types.go b/pkg/kfconfig/dexplugin/types.go new file mode 100644 index 000000000..6ec12980b --- /dev/null +++ b/pkg/kfconfig/dexplugin/types.go @@ -0,0 +1,62 @@ +package dexplugin + +import ( + "github.com/kubeflow/kfctl/v3/pkg/kfconfig" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// +k8s:openapi-gen=true +// Placeholder for the plugin API. +type KfDexPlugin struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec DexPluginSpec `json:"spec,omitempty"` +} + +// AwsPlugin defines the extra data provided by the GCP Plugin in KfDef +type DexPluginSpec struct { + Auth *Auth `json:"auth,omitempty"` + + Domain string `json:"domain,omitempty"` +} + +type Auth struct { + StaticPasswordAuth *StaticPasswordAuth `json:"staticPasswordAuth,omitempty"` + + UseStaticPassword bool `json:"useStaticPassword,omitempty"` +} + +type StaticPasswordAuth struct { + Email string `json:"email,omitempty"` + Password *kfconfig.SecretRef `json:"password,omitempty"` +} + +// IsValid returns true if the spec is a valid and complete spec. +// If false it will also return a string providing a message about why its invalid. +func (plugin *DexPluginSpec) IsValid() (bool, string) { + staticPasswordSet := plugin.Auth.UseStaticPassword + + if staticPasswordSet { + msg := "" + + isValid := true + + if plugin.Auth.StaticPasswordAuth.Email == "" { + isValid = false + msg += "StaticPasswordAuth requires username." + } + + if plugin.Auth.StaticPasswordAuth.Password == nil { + isValid = false + msg += "StaticPasswordAuth requires password." + } + + return isValid, msg + } + + return true, "" + +} diff --git a/pkg/kfconfig/dexplugin/zz_generated.deepcopy.go b/pkg/kfconfig/dexplugin/zz_generated.deepcopy.go new file mode 100644 index 000000000..22744323a --- /dev/null +++ b/pkg/kfconfig/dexplugin/zz_generated.deepcopy.go @@ -0,0 +1,116 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package dexplugin + +import ( + kfconfig "github.com/kubeflow/kfctl/v3/pkg/kfconfig" + 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 *Auth) DeepCopyInto(out *Auth) { + *out = *in + if in.StaticPasswordAuth != nil { + in, out := &in.StaticPasswordAuth, &out.StaticPasswordAuth + *out = new(StaticPasswordAuth) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Auth. +func (in *Auth) DeepCopy() *Auth { + if in == nil { + return nil + } + out := new(Auth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DexPluginSpec) DeepCopyInto(out *DexPluginSpec) { + *out = *in + if in.Auth != nil { + in, out := &in.Auth, &out.Auth + *out = new(Auth) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DexPluginSpec. +func (in *DexPluginSpec) DeepCopy() *DexPluginSpec { + if in == nil { + return nil + } + out := new(DexPluginSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KfDexPlugin) DeepCopyInto(out *KfDexPlugin) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KfDexPlugin. +func (in *KfDexPlugin) DeepCopy() *KfDexPlugin { + if in == nil { + return nil + } + out := new(KfDexPlugin) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *KfDexPlugin) 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 *StaticPasswordAuth) DeepCopyInto(out *StaticPasswordAuth) { + *out = *in + if in.Password != nil { + in, out := &in.Password, &out.Password + *out = new(kfconfig.SecretRef) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StaticPasswordAuth. +func (in *StaticPasswordAuth) DeepCopy() *StaticPasswordAuth { + if in == nil { + return nil + } + out := new(StaticPasswordAuth) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/kfconfig/loaders/utils.go b/pkg/kfconfig/loaders/utils.go index 0723fcea7..c9b5beaa4 100644 --- a/pkg/kfconfig/loaders/utils.go +++ b/pkg/kfconfig/loaders/utils.go @@ -7,9 +7,9 @@ import ( func maybeGetPlatform(pluginKind string) string { platforms := map[string]string{ - string(kfconfig.AWS_PLUGIN_KIND): kftypesv3.AWS, - string(kfconfig.GCP_PLUGIN_KIND): kftypesv3.GCP, - string(kfconfig.EXISTING_ARRIKTO_PLUGIN_KIND): kftypesv3.EXISTING_ARRIKTO, + string(kfconfig.AWS_PLUGIN_KIND): kftypesv3.AWS, + string(kfconfig.GCP_PLUGIN_KIND): kftypesv3.GCP, + string(kfconfig.DEX_PLUGIN_KIND): kftypesv3.DEX, } p, ok := platforms[pluginKind] diff --git a/pkg/kfconfig/loaders/v1alpha1.go b/pkg/kfconfig/loaders/v1alpha1.go index 43787c06b..e0faba5e0 100644 --- a/pkg/kfconfig/loaders/v1alpha1.go +++ b/pkg/kfconfig/loaders/v1alpha1.go @@ -2,6 +2,7 @@ package loaders import ( "fmt" + "github.com/ghodss/yaml" configsv3 "github.com/kubeflow/kfctl/v3/config" kfapis "github.com/kubeflow/kfctl/v3/pkg/apis" @@ -17,10 +18,10 @@ type V1alpha1 struct { func pluginNameToKind(pluginName string) kfconfig.PluginKindType { mapper := map[string]kfconfig.PluginKindType{ - kftypesv3.AWS: kfconfig.AWS_PLUGIN_KIND, - kftypesv3.GCP: kfconfig.GCP_PLUGIN_KIND, - kftypesv3.MINIKUBE: kfconfig.MINIKUBE_PLUGIN_KIND, - kftypesv3.EXISTING_ARRIKTO: kfconfig.EXISTING_ARRIKTO_PLUGIN_KIND, + kftypesv3.AWS: kfconfig.AWS_PLUGIN_KIND, + kftypesv3.GCP: kfconfig.GCP_PLUGIN_KIND, + kftypesv3.MINIKUBE: kfconfig.MINIKUBE_PLUGIN_KIND, + kftypesv3.DEX: kfconfig.DEX_PLUGIN_KIND, } kind, ok := mapper[pluginName] if ok { diff --git a/pkg/kfconfig/types.go b/pkg/kfconfig/types.go index 9310be859..72a0d13c4 100644 --- a/pkg/kfconfig/types.go +++ b/pkg/kfconfig/types.go @@ -176,10 +176,10 @@ const ( // Plugin kind used starting from v1beta1 const ( - AWS_PLUGIN_KIND PluginKindType = "KfAwsPlugin" - GCP_PLUGIN_KIND PluginKindType = "KfGcpPlugin" - MINIKUBE_PLUGIN_KIND PluginKindType = "KfMinikubePlugin" - EXISTING_ARRIKTO_PLUGIN_KIND PluginKindType = "KfExistingArriktoPlugin" + AWS_PLUGIN_KIND PluginKindType = "KfAwsPlugin" + DEX_PLUGIN_KIND PluginKindType = "KfDexPlugin" + GCP_PLUGIN_KIND PluginKindType = "KfGcpPlugin" + MINIKUBE_PLUGIN_KIND PluginKindType = "KfMinikubePlugin" ) type ConditionType string diff --git a/pkg/utils/k8utils.go b/pkg/utils/k8utils.go index 52462563d..c56637aac 100644 --- a/pkg/utils/k8utils.go +++ b/pkg/utils/k8utils.go @@ -432,7 +432,7 @@ func (a *Apply) namespace(namespace string) error { ObjectMeta: metav1.ObjectMeta{ Name: namespace, Labels: map[string]string{ - controlPlaneLabel: "kubeflow", + controlPlaneLabel: "kubeflow", katibMetricsCollectorLabel: "enabled", }, },