From 1eb0453ce4bd48788aea0b96242abdadd3af2ded Mon Sep 17 00:00:00 2001 From: prithviramesh Date: Mon, 5 Feb 2018 01:49:26 -0800 Subject: [PATCH] Functions have access to secrets/configmaps specified by the user (#399) This commit solves part of the issue #52 , functions are able to access secrets/configmaps specified by the user. For now, CLI only accept one secret/configmap. For advanced users, it will be able to use YAML to declare multiple secrets/configmaps in later changes. --- buildermgr/envwatcher.go | 9 +- environments/fetcher/cmd/main.go | 15 +- environments/fetcher/fetcher.go | 151 +++++++++++++++--- executor/newdeploy/newdeploy.go | 6 +- executor/newdeploy/newdeploymgr.go | 4 + executor/poolmgr/gp.go | 47 +++++- fission/function.go | 33 +++- fission/main.go | 6 +- test/test_utils.sh | 27 +++- .../test_secret_cfgmap/cfgmap.py.template | 5 + test/tests/test_secret_cfgmap/empty.py | 7 + .../test_secret_cfgmap/secret.py.template | 6 + .../test_secret_cfgmap/test_secret_cfgmap.sh | 113 +++++++++++++ types.go | 13 ++ 14 files changed, 411 insertions(+), 31 deletions(-) create mode 100644 test/tests/test_secret_cfgmap/cfgmap.py.template create mode 100644 test/tests/test_secret_cfgmap/empty.py create mode 100644 test/tests/test_secret_cfgmap/secret.py.template create mode 100755 test/tests/test_secret_cfgmap/test_secret_cfgmap.sh diff --git a/buildermgr/envwatcher.go b/buildermgr/envwatcher.go index 7e4ddf1a..ed6f694b 100644 --- a/buildermgr/envwatcher.go +++ b/buildermgr/envwatcher.go @@ -417,7 +417,9 @@ func (envw *environmentWatcher) getBuilderDeploymentList(sel map[string]string) } func (envw *environmentWatcher) createBuilderDeployment(env *crd.Environment) (*v1beta1.Deployment, error) { - sharedMountPath := "/package" + sharedMountPath := "/packages" + sharedCfgMapPath := "/configs" + sharedSecretPath := "/secrets" name := envw.getCacheKey(env.Metadata.Name, env.Metadata.ResourceVersion) sel := envw.getLabels(env.Metadata.Name, env.Metadata.ResourceVersion) var replicas int32 = 1 @@ -483,7 +485,10 @@ func (envw *environmentWatcher) createBuilderDeployment(env *crd.Environment) (* MountPath: sharedMountPath, }, }, - Command: []string{"/fetcher", sharedMountPath}, + Command: []string{"/fetcher", + "-secret-dir", sharedSecretPath, + "-cfgmap-dir", sharedCfgMapPath, + sharedMountPath}, ReadinessProbe: &apiv1.Probe{ InitialDelaySeconds: 5, PeriodSeconds: 2, diff --git a/environments/fetcher/cmd/main.go b/environments/fetcher/cmd/main.go index 5c837875..52c20eb1 100644 --- a/environments/fetcher/cmd/main.go +++ b/environments/fetcher/cmd/main.go @@ -20,9 +20,12 @@ import ( // Usage: fetcher func main() { flag.Usage = fetcherUsage + specializeOnStart := flag.Bool("specialize-on-startup", false, "Flag to activate specialize process at pod starup") fetchPayload := flag.String("fetch-request", "", "JSON Payload for fetch request") loadPayload := flag.String("load-request", "", "JSON payload for Load request") - specializeOnStart := flag.Bool("specialize-on-startup", false, "Flag to activate specialize process at pod starup") + secretDir := flag.String("secret-dir", "", "Path to shared secrets directory") + configDir := flag.String("cfgmap-dir", "", "Path to shared configmap directory") + flag.Parse() if flag.NArg() == 0 { flag.Usage() @@ -39,7 +42,7 @@ func main() { } } - fetcher := fetcher.MakeFetcher(dir) + fetcher := fetcher.MakeFetcher(dir, *secretDir, *configDir) if *specializeOnStart { specializePod(fetcher, fetchPayload, loadPayload) @@ -55,7 +58,7 @@ func main() { } func fetcherUsage() { - fmt.Printf("Usage: fetcher [-specialize-on-startup] [-fetch-request ] [-load-request ] \n") + fmt.Printf("Usage: fetcher [-specialize-on-startup] [-fetch-request ] [-load-request ] [-secret-dir ] [-cfgmap-dir ] \n") } func specializePod(f *fetcher.Fetcher, fetchPayload *string, loadPayload *string) { @@ -70,6 +73,12 @@ func specializePod(f *fetcher.Fetcher, fetchPayload *string, loadPayload *string log.Fatalf("Error fetching: %v", err) } + _, err = f.FetchSecretsAndCfgMaps(fetchReq.Secrets, fetchReq.ConfigMaps) + if err != nil { + log.Fatalf("Error fetching secerts/configmaps: %v", err) + return + } + // Specialize the pod envVersion, err := strconv.Atoi(os.Getenv("ENV_VERSION")) diff --git a/environments/fetcher/fetcher.go b/environments/fetcher/fetcher.go index 46123299..6ce11576 100644 --- a/environments/fetcher/fetcher.go +++ b/environments/fetcher/fetcher.go @@ -16,7 +16,9 @@ import ( "github.com/mholt/archiver" "github.com/satori/go.uuid" + k8serr "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" "github.com/fission/fission" "github.com/fission/fission/crd" @@ -27,11 +29,13 @@ type ( FetchRequestType int FetchRequest struct { - FetchType FetchRequestType `json:"fetchType"` - Package metav1.ObjectMeta `json:"package"` - Url string `json:"url"` - StorageSvcUrl string `json:"storagesvcurl"` - Filename string `json:"filename"` + FetchType FetchRequestType `json:"fetchType"` + Package metav1.ObjectMeta `json:"package"` + Url string `json:"url"` + StorageSvcUrl string `json:"storagesvcurl"` + Filename string `json:"filename"` + Secrets []fission.SecretReference `json:"secretList"` + ConfigMaps []fission.ConfigMapReference `json:"configMapList"` } // UploadRequest send from builder manager describes which @@ -50,7 +54,10 @@ type ( Fetcher struct { sharedVolumePath string + sharedSecretPath string + sharedConfigPath string fissionClient *crd.FissionClient + kubeClient *kubernetes.Clientset } ) @@ -60,14 +67,28 @@ const ( FETCH_URL // remove this? ) -func MakeFetcher(sharedVolumePath string) *Fetcher { - fissionClient, _, _, err := crd.MakeFissionClient() +func makeVolumeDir(dirPath string) { + err := os.MkdirAll(dirPath, os.ModeDir|0700) + if err != nil { + log.Fatalf("Error creating %v: %v", dirPath, err) + } +} + +func MakeFetcher(sharedVolumePath string, sharedSecretPath string, sharedConfigPath string) *Fetcher { + makeVolumeDir(sharedVolumePath) + makeVolumeDir(sharedSecretPath) + makeVolumeDir(sharedConfigPath) + + fissionClient, kubeClient, _, err := crd.MakeFissionClient() if err != nil { return nil } return &Fetcher{ sharedVolumePath: sharedVolumePath, + sharedSecretPath: sharedSecretPath, + sharedConfigPath: sharedConfigPath, fissionClient: fissionClient, + kubeClient: kubeClient, } } @@ -129,9 +150,22 @@ func verifyChecksum(path string, checksum *fission.Checksum) error { return nil } +func writeSecretOrConfigMap(dataMap map[string][]byte, dirPath string) error { + for key, val := range dataMap { + writeFilePath := filepath.Join(dirPath, key) + err := ioutil.WriteFile(writeFilePath, val, 0600) + if err != nil { + e := fmt.Sprintf("Failed to write file %v: %v", writeFilePath, err) + log.Printf(e) + return errors.New(e) + } + } + return nil +} + func (fetcher *Fetcher) FetchHandler(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { - http.Error(w, "only POST is supported on this endpoint", 405) + http.Error(w, "only POST is supported on this endpoint", http.StatusMethodNotAllowed) return } @@ -145,24 +179,32 @@ func (fetcher *Fetcher) FetchHandler(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { log.Printf("Error reading request body") - http.Error(w, err.Error(), 500) + http.Error(w, err.Error(), http.StatusInternalServerError) return } var req FetchRequest err = json.Unmarshal(body, &req) if err != nil { log.Printf("Error reading request body: %v", err) - http.Error(w, err.Error(), 400) + http.Error(w, err.Error(), http.StatusBadRequest) return } - log.Printf("fetcher received fetch request and started downloading: %v", req) + log.Printf("fetcher received fetch request and started downloading: %v", req) code, err := fetcher.Fetch(req) if err != nil { http.Error(w, err.Error(), code) return } + log.Printf("Checking secrets/cfgmaps") + code, err = fetcher.FetchSecretsAndCfgMaps(req.Secrets, req.ConfigMaps) + if err != nil { + http.Error(w, err.Error(), code) + return + } + + log.Printf("Completed fetch request") // all done w.WriteHeader(http.StatusOK) } @@ -241,13 +283,86 @@ func (fetcher *Fetcher) Fetch(req FetchRequest) (int, error) { log.Println(err.Error()) return 500, err } + log.Printf("Successfully placed at %v", filepath.Join(fetcher.sharedVolumePath, req.Filename)) return 200, nil } +// FetchSecretsAndCfgMaps fetches secrets and configmaps specified by user +// It returns the HTTP code and error if any +func (fetcher *Fetcher) FetchSecretsAndCfgMaps(secrets []fission.SecretReference, cfgmaps []fission.ConfigMapReference) (int, error) { + if len(secrets) > 0 { + for _, secret := range secrets { + data, err := fetcher.kubeClient.CoreV1().Secrets(secret.Namespace).Get(secret.Name, metav1.GetOptions{}) + + if err != nil { + e := fmt.Sprintf("Failed to get secret from kubeapi: %v", err) + log.Printf(e) + + httpCode := http.StatusInternalServerError + if k8serr.IsNotFound(err) { + httpCode = http.StatusNotFound + } + + return httpCode, errors.New(e) + } + + secretPath := filepath.Join(secret.Namespace, secret.Name) + secretDir := filepath.Join(fetcher.sharedSecretPath, secretPath) + err = os.MkdirAll(secretDir, os.ModeDir|0644) + if err != nil { + e := fmt.Sprintf("Failed to create directory %v: %v", secretDir, err) + log.Printf(e) + return http.StatusInternalServerError, errors.New(e) + } + err = writeSecretOrConfigMap(data.Data, secretDir) + if err != nil { + return http.StatusInternalServerError, err + } + } + } + + if len(cfgmaps) > 0 { + for _, config := range cfgmaps { + data, err := fetcher.kubeClient.CoreV1().ConfigMaps(config.Namespace).Get(config.Name, metav1.GetOptions{}) + + if err != nil { + e := fmt.Sprintf("Failed to get configmap from kubeapi: %v", err) + log.Printf(e) + + httpCode := http.StatusInternalServerError + if k8serr.IsNotFound(err) { + httpCode = http.StatusNotFound + } + + return httpCode, errors.New(e) + } + + configPath := filepath.Join(config.Namespace, config.Name) + configDir := filepath.Join(fetcher.sharedConfigPath, configPath) + err = os.MkdirAll(configDir, os.ModeDir|0644) + if err != nil { + e := fmt.Sprintf("Failed to create directory %v: %v", configDir, err) + log.Printf(e) + return http.StatusInternalServerError, errors.New(e) + } + configMap := make(map[string][]byte) + for key, val := range data.Data { + configMap[key] = []byte(val) + } + err = writeSecretOrConfigMap(configMap, configDir) + if err != nil { + return http.StatusInternalServerError, err + } + } + } + + return http.StatusOK, nil +} + func (fetcher *Fetcher) UploadHandler(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { - http.Error(w, "only POST is supported on this endpoint", 405) + http.Error(w, "only POST is supported on this endpoint", http.StatusMethodNotAllowed) return } @@ -261,7 +376,7 @@ func (fetcher *Fetcher) UploadHandler(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { log.Printf("Error reading request body") - http.Error(w, err.Error(), 500) + http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -269,7 +384,7 @@ func (fetcher *Fetcher) UploadHandler(w http.ResponseWriter, r *http.Request) { err = json.Unmarshal(body, &req) if err != nil { log.Printf("Error reading request body: %v", err) - http.Error(w, err.Error(), 400) + http.Error(w, err.Error(), http.StatusBadRequest) return } log.Printf("fetcher received upload request: %v", req) @@ -282,7 +397,7 @@ func (fetcher *Fetcher) UploadHandler(w http.ResponseWriter, r *http.Request) { if err != nil { e := fmt.Sprintf("Error archiving zip file: %v", err) log.Println(e) - http.Error(w, e, 500) + http.Error(w, e, http.StatusInternalServerError) return } @@ -293,7 +408,7 @@ func (fetcher *Fetcher) UploadHandler(w http.ResponseWriter, r *http.Request) { if err != nil { e := fmt.Sprintf("Error uploading zip file: %v", err) log.Println(e) - http.Error(w, e, 500) + http.Error(w, e, http.StatusInternalServerError) return } @@ -301,7 +416,7 @@ func (fetcher *Fetcher) UploadHandler(w http.ResponseWriter, r *http.Request) { if err != nil { e := fmt.Sprintf("Error calculating checksum of zip file: %v", err) log.Println(e) - http.Error(w, e, 500) + http.Error(w, e, http.StatusInternalServerError) return } @@ -314,7 +429,7 @@ func (fetcher *Fetcher) UploadHandler(w http.ResponseWriter, r *http.Request) { if err != nil { e := fmt.Sprintf("Error encoding upload response: %v", err) log.Println(e) - http.Error(w, e, 500) + http.Error(w, e, http.StatusInternalServerError) return } diff --git a/executor/newdeploy/newdeploy.go b/executor/newdeploy/newdeploy.go index 1b833c3d..5efb6b8a 100644 --- a/executor/newdeploy/newdeploy.go +++ b/executor/newdeploy/newdeploy.go @@ -67,7 +67,9 @@ func (deploy *NewDeploy) createOrGetDeployment(fn *crd.Function, env *crd.Enviro Namespace: fn.Spec.Package.PackageRef.Namespace, Name: fn.Spec.Package.PackageRef.Name, }, - Filename: targetFilename, + Filename: targetFilename, + Secrets: fn.Spec.Secrets, + ConfigMaps: fn.Spec.ConfigMaps, } loadReq := fission.FunctionLoadRequest{ @@ -136,6 +138,8 @@ func (deploy *NewDeploy) createOrGetDeployment(fn *crd.Function, env *crd.Enviro Command: []string{"/fetcher", "-specialize-on-startup", "-fetch-request", string(fetchPayload), "-load-request", string(loadPayload), + "-secret-dir", deploy.sharedSecretPath, + "-cfgmap-dir", deploy.sharedCfgMapPath, deploy.sharedMountPath}, Env: []apiv1.EnvVar{ { diff --git a/executor/newdeploy/newdeploymgr.go b/executor/newdeploy/newdeploymgr.go index b3ea3c7b..f16616e8 100644 --- a/executor/newdeploy/newdeploymgr.go +++ b/executor/newdeploy/newdeploymgr.go @@ -48,6 +48,8 @@ type ( fetcherImagePullPolicy apiv1.PullPolicy namespace string sharedMountPath string + sharedSecretPath string + sharedCfgMapPath string fsCache *fscache.FunctionServiceCache // cache funcSvc's by function, address and podname requestChannel chan *fnRequest @@ -107,6 +109,8 @@ func MakeNewDeploy( fetcherImg: fetcherImg, fetcherImagePullPolicy: apiv1.PullIfNotPresent, sharedMountPath: "/userfunc", + sharedSecretPath: "/secrets", + sharedCfgMapPath: "/configs", requestChannel: make(chan *fnRequest), } diff --git a/executor/poolmgr/gp.go b/executor/poolmgr/gp.go index a7a1df18..bfec9df8 100644 --- a/executor/poolmgr/gp.go +++ b/executor/poolmgr/gp.go @@ -69,6 +69,8 @@ type ( labelsForPool map[string]string requestChannel chan *choosePodRequest sharedMountPath string // used by generic pool when creating env deployment to specify the share volume path for fetcher & env + sharedSecretPath string + sharedCfgMapPath string } // serialize the choosing of pods so that choices don't conflict @@ -134,6 +136,8 @@ func MakeGenericPool( fetcherImage: fetcherImage, useSvc: false, // defaults off -- svc takes a second or more to become routable, slowing cold start sharedMountPath: "/userfunc", // change this may break v1 compatibility, since most of the v1 environments have hard-coded "/userfunc" in loading path + sharedSecretPath: "/secrets", + sharedCfgMapPath: "/configs", } gp.runtimeImagePullPolicy = getImagePullPolicy(runtimeImagePullPolicy) @@ -360,7 +364,9 @@ func (gp *GenericPool) specializePod(pod *apiv1.Pod, metadata *metav1.ObjectMeta Namespace: fn.Spec.Package.PackageRef.Namespace, Name: fn.Spec.Package.PackageRef.Name, }, - Filename: targetFilename, + Filename: targetFilename, + Secrets: fn.Spec.Secrets, + ConfigMaps: fn.Spec.ConfigMaps, }) if err != nil { return err @@ -449,6 +455,20 @@ func (gp *GenericPool) createPool() error { EmptyDir: &apiv1.EmptyDirVolumeSource{}, }, }, + + { + Name: "secrets", + VolumeSource: apiv1.VolumeSource{ + EmptyDir: &apiv1.EmptyDirVolumeSource{}, + }, + }, + + { + Name: "config", + VolumeSource: apiv1.VolumeSource{ + EmptyDir: &apiv1.EmptyDirVolumeSource{}, + }, + }, }, Containers: []apiv1.Container{ { @@ -461,6 +481,16 @@ func (gp *GenericPool) createPool() error { Name: "userfunc", MountPath: gp.sharedMountPath, }, + + { + Name: "secrets", + MountPath: gp.sharedSecretPath, + }, + + { + Name: "config", + MountPath: gp.sharedCfgMapPath, + }, }, Resources: gp.env.Spec.Resources, }, @@ -474,8 +504,21 @@ func (gp *GenericPool) createPool() error { Name: "userfunc", MountPath: gp.sharedMountPath, }, + + { + Name: "secrets", + MountPath: gp.sharedSecretPath, + }, + + { + Name: "config", + MountPath: gp.sharedCfgMapPath, + }, }, - Command: []string{"/fetcher", gp.sharedMountPath}, + Command: []string{"/fetcher", + "-secret-dir", gp.sharedSecretPath, + "-cfgmap-dir", gp.sharedCfgMapPath, + gp.sharedMountPath}, }, }, ServiceAccountName: "fission-fetcher", diff --git a/fission/function.go b/fission/function.go index 6f64c13e..77bea125 100644 --- a/fission/function.go +++ b/fission/function.go @@ -126,6 +126,20 @@ func fnCreate(c *cli.Context) error { var pkgMetadata *metav1.ObjectMeta var envName string + secretName := c.String("secret") + cfgMapName := c.String("configmap") + + secretNameSpace := c.String("secretNamespace") + cfgMapNameSpace := c.String("configmapNamespace") + + if len(secretNameSpace) == 0 && len(secretName) > 0 { + secretNameSpace = metav1.NamespaceDefault + } + + if len(cfgMapNameSpace) == 0 && len(cfgMapName) > 0 { + cfgMapNameSpace = metav1.NamespaceDefault + } + if len(pkgName) > 0 { // use existing package pkg, err := client.PackageGet(&metav1.ObjectMeta{ @@ -204,11 +218,29 @@ func fnCreate(c *cli.Context) error { ResourceVersion: pkgMetadata.ResourceVersion, }, }, + Secrets: []fission.SecretReference{}, + ConfigMaps: []fission.ConfigMapReference{}, Resources: resourceReq, InvokeStrategy: invokeStrategy, }, } + if len(secretName) > 0 { + newSecret := fission.SecretReference{ + Name: secretName, + Namespace: secretNameSpace, + } + function.Spec.Secrets = append(function.Spec.Secrets, newSecret) + } + + if len(cfgMapName) > 0 { + newCfgMap := fission.ConfigMapReference{ + Name: cfgMapName, + Namespace: cfgMapNameSpace, + } + function.Spec.ConfigMaps = append(function.Spec.ConfigMaps, newCfgMap) + } + _, err = client.FunctionCreate(function) checkErr(err, "create function") @@ -338,7 +370,6 @@ func fnUpdate(c *cli.Context) error { if len(entrypoint) > 0 { function.Spec.Package.FunctionName = entrypoint } - if len(pkgName) == 0 { pkgName = function.Spec.Package.PackageRef.Name } diff --git a/fission/main.go b/fission/main.go index f8cb2f0a..2cd9e1e0 100644 --- a/fission/main.go +++ b/fission/main.go @@ -61,12 +61,16 @@ func main() { fnHeaderFlag := cli.StringSliceFlag{Name: "header, H", Usage: "request headers"} fnEntryPointFlag := cli.StringFlag{Name: "entrypoint", Usage: "entry point for environment v2 to load with"} fnBuildCmdFlag := cli.StringFlag{Name: "buildcmd", Usage: "build command for builder to run with"} + fnSecretFlag := cli.StringFlag{Name: "secret", Usage: "function access to secret"} + fnSecretnsFlag := cli.StringFlag{Name: "secretNamespace", Usage: "namespace of secret"} + fnCfgMapFlag := cli.StringFlag{Name: "configmap", Usage: "function access to configmap"} + fnCfgMapnsFlag := cli.StringFlag{Name: "configmapNamespace", Usage: "namespace of configmap"} fnLogCountFlag := cli.StringFlag{Name: "recordcount", Usage: "the n most recent log records"} fnForceFlag := cli.BoolFlag{Name: "force", Usage: "Force update a package even if it is used by one or more functions"} fnExecutorTypeFlag := cli.StringFlag{Name: "executortype", Usage: "Executor type for execution; one of 'poolmgr', 'newdeploy' defaults to 'poolmgr'"} fnSubcommands := []cli.Command{ - {Name: "create", Usage: "Create new function (and optionally, an HTTP route to it)", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, fnPackageFlag, fnSrcArchiveFlag, fnDeployArchiveFlag, fnEntryPointFlag, fnBuildCmdFlag, fnPkgNameFlag, htUrlFlag, htMethodFlag, minCpu, maxCpu, minMem, maxMem, minScale, maxScale, fnExecutorTypeFlag, targetcpu}, Action: fnCreate}, + {Name: "create", Usage: "Create new function (and optionally, an HTTP route to it)", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, fnPackageFlag, fnSrcArchiveFlag, fnDeployArchiveFlag, fnEntryPointFlag, fnBuildCmdFlag, fnPkgNameFlag, htUrlFlag, htMethodFlag, minCpu, maxCpu, minMem, maxMem, minScale, maxScale, fnExecutorTypeFlag, targetcpu, fnCfgMapFlag, fnSecretFlag, fnSecretnsFlag, fnCfgMapnsFlag}, Action: fnCreate}, {Name: "get", Usage: "Get function source code", Flags: []cli.Flag{fnNameFlag}, Action: fnGet}, {Name: "getmeta", Usage: "Get function metadata", Flags: []cli.Flag{fnNameFlag}, Action: fnGetMeta}, {Name: "update", Usage: "Update function source code", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, fnPackageFlag, fnSrcArchiveFlag, fnDeployArchiveFlag, fnEntryPointFlag, fnPkgNameFlag, fnBuildCmdFlag, fnForceFlag, minCpu, maxCpu, minMem, maxMem, minScale, maxScale, fnExecutorTypeFlag, targetcpu}, Action: fnUpdate}, diff --git a/test/test_utils.sh b/test/test_utils.sh index 7513bfb5..f660409a 100755 --- a/test/test_utils.sh +++ b/test/test_utils.sh @@ -240,6 +240,25 @@ set_environment() { export PATH=$ROOT/fission:$PATH } +dump_builder_pod_logs() { + bns=$1 + builderPods=$(kubectl -n $bns get pod -o name) + + for p in $builderPods + do + echo "--- builder pod logs $p ---" + containers=$(kubectl -n $bns get $p -o jsonpath={.spec.containers[*].name} --ignore-not-found) + for c in $containers + do + echo "--- builder pod logs $p: container $c ---" + kubectl -n $bns logs $p $c || true + echo "--- end builder pod logs $p: container $c ---" + done + echo "--- end builder pod logs $p ---" + done + +} + dump_function_pod_logs() { ns=$1 fns=$2 @@ -248,11 +267,11 @@ dump_function_pod_logs() { for p in $functionPods do echo "--- function pod logs $p ---" - containers=$(kubectl -n $fns get $p -o jsonpath={.spec.containers[*].name}) + containers=$(kubectl -n $fns get $p -o jsonpath={.spec.containers[*].name} --ignore-not-found) for c in $containers do echo "--- function pod logs $p: container $c ---" - kubectl -n $fns logs $p $c + kubectl -n $fns logs $p $c || true echo "--- end function pod logs $p: container $c ---" done echo "--- end function pod logs $p ---" @@ -265,7 +284,7 @@ dump_fission_logs() { component=$3 echo --- $component logs --- - kubectl -n $ns get pod -o name | grep $component | xargs kubectl -n $ns logs + kubectl -n $ns get pod -o name | grep $component | xargs kubectl -n $ns logs echo --- end $component logs --- } @@ -316,6 +335,7 @@ dump_logs() { ns=f-$id fns=f-func-$id + bns=fission-builder dump_all_fission_resources $ns dump_env_pods $fns @@ -324,6 +344,7 @@ dump_logs() { dump_fission_logs $ns $fns buildermgr dump_fission_logs $ns $fns executor dump_function_pod_logs $ns $fns + dump_builder_pod_logs $bns dump_fission_crds } diff --git a/test/tests/test_secret_cfgmap/cfgmap.py.template b/test/tests/test_secret_cfgmap/cfgmap.py.template new file mode 100644 index 00000000..af137677 --- /dev/null +++ b/test/tests/test_secret_cfgmap/cfgmap.py.template @@ -0,0 +1,5 @@ +def main(): + path = "/configs/default/{{ FN_CFGMAP }}/TEST_KEY" + f = open(path, "r") + data = f.read() + return data, 200 diff --git a/test/tests/test_secret_cfgmap/empty.py b/test/tests/test_secret_cfgmap/empty.py new file mode 100644 index 00000000..3f4bd3f0 --- /dev/null +++ b/test/tests/test_secret_cfgmap/empty.py @@ -0,0 +1,7 @@ +import os +def main(): + cfgmap_path = "/configs/" + secret_path = "/secrets/" + if os.listdir(cfgmap_path) or os.listdir(secret_path): + return "no", 400 + return "yes", 200 diff --git a/test/tests/test_secret_cfgmap/secret.py.template b/test/tests/test_secret_cfgmap/secret.py.template new file mode 100644 index 00000000..fd443a35 --- /dev/null +++ b/test/tests/test_secret_cfgmap/secret.py.template @@ -0,0 +1,6 @@ +def main(): + path = "/secrets/default/{{ FN_SECRET }}/TEST_KEY" + f = open(path, "r") + data = f.read() + #print() + return data, 200 diff --git a/test/tests/test_secret_cfgmap/test_secret_cfgmap.sh b/test/tests/test_secret_cfgmap/test_secret_cfgmap.sh new file mode 100755 index 00000000..20a2933e --- /dev/null +++ b/test/tests/test_secret_cfgmap/test_secret_cfgmap.sh @@ -0,0 +1,113 @@ +#!/bin/bash + +set -euo pipefail + +ROOT=$(dirname $0)/../.. + +fn=testnormal-$(date +%s) +fn_secret=testsecret-$(date +%s) +fn_cfgmap=testcfgmap-$(date +%s) + +cp secret.py.template secret.py +sed -i "s/{{ FN_SECRET }}/${fn_secret}/g" secret.py + +cp cfgmap.py.template cfgmap.py +sed -i "s/{{ FN_CFGMAP }}/${fn_cfgmap}/g" cfgmap.py + +function cleanup { + echo "Cleanup everything" + kubectl delete secret -n default ${fn_secret} + kubectl delete configmap -n default ${fn_cfgmap} + fission function delete --name ${fn_secret} + fission function delete --name ${fn_cfgmap} + fission function delete --name ${fn} + var=$(fission route list | grep ${fn_secret} | awk '{print $1;}') + var2=$(fission route list | grep ${fn_cfgmap} | awk '{print $1;}') + var3=$(fission route list | grep ${fn} | awk '{print $1;}') + fission route delete --name ${var} + fission route delete --name ${var2} + fission route delete --name ${var3} +} + +# Create a hello world function in nodejs, test it with an http trigger +echo "Pre-test cleanup" +fission env delete --name python || true + +echo "Creating python env" +fission env create --name python --image fission/python-env +trap "fission env delete --name python" EXIT + +echo "Creating secret" +kubectl create secret generic ${fn_secret} --from-literal=TEST_KEY="TESTVALUE" -n default +trap "kubectl delete secret ${fn_secret} -n default" EXIT + + +echo "Creating function with secret" +fission fn create --name ${fn_secret} --env python --code secret.py --secret ${fn_secret} +trap "fission fn delete --name ${fn_secret}" EXIT + +echo "Creating route" +fission route create --function ${fn_secret} --url /${fn_secret} --method GET + +echo "Waiting for router to catch up" +sleep 5 + +echo "HTTP GET on the function's route" +res=$(curl http://${FISSION_ROUTER}/${fn_secret}) +val='TESTVALUE' + +if [[ ${res} != ${val} ]] +then + echo "test secret failed" + cleanup + exit 1 +fi +echo "test secret passed" + +echo "Creating configmap" +kubectl create configmap ${fn_cfgmap} --from-literal=TEST_KEY=TESTVALUE -n default +trap "kubectl delete configmap ${fn_cfgmap} -n default" EXIT + +echo "creating function with configmap" +fission fn create --name ${fn_cfgmap} --env python --code cfgmap.py --configmap ${fn_cfgmap} +trap "fission fn delete --name ${fn_cfgmap}" EXIT + +echo "Creating route" +fission route create --function ${fn_cfgmap} --url /${fn_cfgmap} --method GET + +echo "Waiting for router to catch up" +sleep 5 + +echo "HTTP GET on the function's route" +rescfg=$(curl http://${FISSION_ROUTER}/${fn_cfgmap}) + +if [ ${rescfg} != ${val} ] +then + echo "test cfgmap failed" + cleanup + exit 1 +fi +echo "test configmap passed" + +echo "testing creating a function without a secret or configmap" +fission function create --name ${fn} --env python --code empty.py +trap "fission fn delete --name ${fn}" EXIT + +echo "Creating route" +fission route create --function ${fn} --url /${fn} --method GET + +echo "Waiting for router to catch up" +sleep 5 + +echo "HTTP GET on the function's route" +resnormal=$(curl http://${FISSION_ROUTER}/${fn}) +if [ ${resnormal} != "yes" ] +then + echo "test empty failed" + cleanup + exit 1 +fi +echo "test empty passed" + +echo "All done." +trap "cleanup" EXIT diff --git a/types.go b/types.go index e085dbb9..2edbaabe 100644 --- a/types.go +++ b/types.go @@ -67,6 +67,16 @@ type ( Name string `json:"name"` } + SecretReference struct { + Namespace string `json:"namespace"` + Name string `json:"name"` + } + + ConfigMapReference struct { + Namespace string `json:"namespace"` + Name string `json:"name"` + } + BuildStatus string PackageSpec struct { @@ -119,6 +129,9 @@ type ( // Reference to a package containing deployment and optionally the source Package FunctionPackageRef `json:"package"` + Secrets []SecretReference `json:"secrets"` + ConfigMaps []ConfigMapReference `json:"configmaps"` + // cpu and memory resources as per K8S standards Resources v1.ResourceRequirements `json:"resources"`