From 7b3cc8fdf01ebd48cd306cfb9d32a569a4c896a6 Mon Sep 17 00:00:00 2001 From: Anubhav <50611570+anubhav6663@users.noreply.github.com> Date: Thu, 19 Dec 2019 17:11:59 +0530 Subject: [PATCH] Add spec list feature (#1468) Adding fission spec list functionality. It lists Functions, Environment, Packages, HTTPTrigger, MessageQueueTriggers, Canary Config, TimeTrigger, KubeWatch created by an application specification. --- pkg/fission-cli/cmd/spec/command.go | 11 +- pkg/fission-cli/cmd/spec/list.go | 454 ++++++++++++++++++++++++++++ 2 files changed, 464 insertions(+), 1 deletion(-) create mode 100644 pkg/fission-cli/cmd/spec/list.go diff --git a/pkg/fission-cli/cmd/spec/command.go b/pkg/fission-cli/cmd/spec/command.go index b4a6307c..5af68151 100644 --- a/pkg/fission-cli/cmd/spec/command.go +++ b/pkg/fission-cli/cmd/spec/command.go @@ -60,13 +60,22 @@ func Commands() *cobra.Command { Optional: []flag.Flag{flag.SpecDir}, }) + listCmd := &cobra.Command{ + Use: "list", + Short: "List all the resources that were created through this spec", + RunE: wrapper.Wrapper(List), + } + wrapper.SetFlags(listCmd, flag.FlagSet{ + Optional: []flag.Flag{flag.SpecDeployID, flag.SpecDir}, + }) + command := &cobra.Command{ Use: "spec", Aliases: []string{"specs"}, Short: "Manage a declarative application specification", } - command.AddCommand(initCmd, validateCmd, applyCmd, destroyCmd) + command.AddCommand(initCmd, validateCmd, applyCmd, listCmd, destroyCmd) return command } diff --git a/pkg/fission-cli/cmd/spec/list.go b/pkg/fission-cli/cmd/spec/list.go new file mode 100644 index 00000000..733d5a47 --- /dev/null +++ b/pkg/fission-cli/cmd/spec/list.go @@ -0,0 +1,454 @@ +/* +Copyright 2019 The Fission 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 spec + +import ( + "fmt" + "os" + "strings" + "text/tabwriter" + "time" + + "github.com/pkg/errors" + + fv1 "github.com/fission/fission/pkg/apis/fission.io/v1" + "github.com/fission/fission/pkg/fission-cli/cliwrapper/cli" + "github.com/fission/fission/pkg/fission-cli/cmd" + flagkey "github.com/fission/fission/pkg/fission-cli/flag/key" + "github.com/fission/fission/pkg/fission-cli/util" +) + +// ListSubCommand struct +type ListSubCommand struct { + cmd.CommandActioner +} + +// List lists resources in the spec. +func List(input cli.Input) error { + return (&ListSubCommand{}).do(input) +} + +func (opts *ListSubCommand) do(input cli.Input) error { + return opts.run(input) +} + +func (opts *ListSubCommand) run(input cli.Input) error { + deployID := input.String(flagkey.SpecDeployID) + if len(deployID) == 0 { + // get specdir and read the deployID + specDir := util.GetSpecDir(input) + fr, err := ReadSpecs(specDir) + if err != nil { + return errors.Wrap(err, "error reading specs") + } + deployID = fr.DeploymentConfig.UID + } + + allfn, err := getAllFunctions(opts) + if err != nil { + return errors.Wrap(err, "error getting Functions from all namespaces") + } + specfns := getAppliedFunctions(allfn, deployID) + ShowFunctions(specfns) + + allenvs, err := getAllEnvironments(opts) + if err != nil { + return errors.Wrap(err, "error getting Environments from all namespaces") + } + specenvs := getAppliedEnvironments(allenvs, deployID) + ShowEnvironments(specenvs) + + pkglists, err := getAllPackages(opts) + if err != nil { + return errors.Wrap(err, "error getting Packages from all namespaces") + } + specPkgs := getAppliedPackages(pkglists, deployID) + ShowPackages(specPkgs) + + canaryCfgs, err := getAllCanaryConfigs(opts) + if err != nil { + return errors.Wrap(err, "error getting Canary Config from all namespaces") + } + specCanaryCfgs := getAppliedCanaryConfigs(canaryCfgs, deployID) + ShowCanaryConfigs(specCanaryCfgs) + + hts, err := getAllHTTPTriggers(opts) + if err != nil { + return errors.Wrap(err, "error getting HTTP Triggers from all namespaces") + } + specHTTPTriggers := getAppliedHTTPTriggers(hts, deployID) + ShowHTTPTriggers(specHTTPTriggers) + + mqts, err := getAllMessageQueueTriggers(opts, input.String(flagkey.MqtMQType)) + if err != nil { + return errors.Wrap(err, "error getting MessageQueue Triggers from all namespaces") + } + specMessageQueueTriggers := getAppliedMessageQueueTriggers(mqts, deployID) + ShowMQTriggers(specMessageQueueTriggers) + + tts, err := getAllTimeTriggers(opts) + if err != nil { + return errors.Wrap(err, "error getting Time Triggers from all namespaces") + } + specTimeTriggers := getAppliedTimeTriggers(tts, deployID) + ShowTimeTriggers(specTimeTriggers) + + kws, err := getAllKubeWatchTriggers(opts) + if err != nil { + return errors.Wrap(err, "error getting Kube Watchers from all namespaces") + } + specKubeWatchers := getSpecKubeWatchers(kws, deployID) + ShowAppliedKubeWatchers(specKubeWatchers) + + return nil +} + +func getAppliedFunctions(fns []fv1.Function, deployID string) []fv1.Function { + var fnlist []fv1.Function + if len(fns) > 0 { + for _, f := range fns { + if f.Metadata.Annotations["fission-uid"] == deployID { + fnlist = append(fnlist, f) + } + } + } + return fnlist +} + +func getAppliedEnvironments(envs []fv1.Environment, deployID string) []fv1.Environment { + var envlist []fv1.Environment + if len(envs) > 0 { + for _, f := range envs { + if f.Metadata.Annotations["fission-uid"] == deployID { + envlist = append(envlist, f) + } + } + } + return envlist +} + +func getAppliedPackages(pkgs []fv1.Package, deployID string) []fv1.Package { + var pkglist []fv1.Package + if len(pkgs) > 0 { + for _, f := range pkgs { + if f.Metadata.Annotations["fission-uid"] == deployID { + pkglist = append(pkglist, f) + } + } + } + return pkglist +} +func getAppliedCanaryConfigs(canaryCfgs []fv1.CanaryConfig, deployID string) []fv1.CanaryConfig { + var canaryConfiglist []fv1.CanaryConfig + if len(canaryCfgs) > 0 { + for _, f := range canaryCfgs { + if f.Metadata.Annotations["fission-uid"] == deployID { + canaryConfiglist = append(canaryConfiglist, f) + } + } + } + return canaryConfiglist +} + +func getAppliedHTTPTriggers(hts []fv1.HTTPTrigger, deployID string) []fv1.HTTPTrigger { + var httpTriggerlist []fv1.HTTPTrigger + if len(hts) > 0 { + for _, f := range hts { + if f.Metadata.Annotations["fission-uid"] == deployID { + httpTriggerlist = append(httpTriggerlist, f) + } + } + } + return httpTriggerlist + +} + +func getAppliedMessageQueueTriggers(mqts []fv1.MessageQueueTrigger, deployID string) []fv1.MessageQueueTrigger { + var mqTriggerlist []fv1.MessageQueueTrigger + if len(mqts) > 0 { + for _, f := range mqts { + if f.Metadata.Annotations["fission-uid"] == deployID { + mqTriggerlist = append(mqTriggerlist, f) + } + } + } + return mqTriggerlist +} +func getAppliedTimeTriggers(tts []fv1.TimeTrigger, deployID string) []fv1.TimeTrigger { + var timeTriggerlist []fv1.TimeTrigger + if len(tts) > 0 { + for _, f := range tts { + if f.Metadata.Annotations["fission-uid"] == deployID { + timeTriggerlist = append(timeTriggerlist, f) + } + } + } + return timeTriggerlist +} +func getSpecKubeWatchers(ws []fv1.KubernetesWatchTrigger, deployID string) []fv1.KubernetesWatchTrigger { + var kubeWatchTriggerlist []fv1.KubernetesWatchTrigger + if len(ws) > 0 { + for _, f := range ws { + if f.Metadata.Annotations["fission-uid"] == deployID { + kubeWatchTriggerlist = append(kubeWatchTriggerlist, f) + } + } + } + return kubeWatchTriggerlist +} + +// ShowFunctions displays info of Functions +func ShowFunctions(fns []fv1.Function) { + if len(fns) > 0 { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + fmt.Fprintf(w, "%v\n", "Functions:") + fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", "NAME", "ENV", "EXECUTORTYPE", "MINSCALE", "MAXSCALE", "MINCPU", "MAXCPU", "MINMEMORY", "MAXMEMORY", "TARGETCPU", "SECRETS", "CONFIGMAPS") + + for _, f := range fns { + secrets := f.Spec.Secrets + configMaps := f.Spec.ConfigMaps + var secretsList, configMapList []string + for _, secret := range secrets { + secretsList = append(secretsList, secret.Name) + } + for _, configMap := range configMaps { + configMapList = append(configMapList, configMap.Name) + } + + fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", + f.Metadata.Name, f.Spec.Environment.Name, + f.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType, + f.Spec.InvokeStrategy.ExecutionStrategy.MinScale, + f.Spec.InvokeStrategy.ExecutionStrategy.MaxScale, + f.Spec.Resources.Requests.Cpu().String(), + f.Spec.Resources.Limits.Cpu().String(), + f.Spec.Resources.Requests.Memory().String(), + f.Spec.Resources.Limits.Memory().String(), + f.Spec.InvokeStrategy.ExecutionStrategy.TargetCPUPercent, + strings.Join(secretsList, ","), + strings.Join(configMapList, ",")) + } + fmt.Fprintf(w, "\n") + w.Flush() + } +} + +// ShowEnvironments displays info of Environments +func ShowEnvironments(envs []fv1.Environment) { + if len(envs) > 0 { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + fmt.Fprintf(w, "%v\n", "Environments:") + fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", "NAME", "IMAGE", "BUILDER_IMAGE", "POOLSIZE", "MINCPU", "MAXCPU", "MINMEMORY", "MAXMEMORY", "EXTNET", "GRACETIME") + for _, env := range envs { + fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", + env.Metadata.Name, env.Spec.Runtime.Image, env.Spec.Builder.Image, env.Spec.Poolsize, + env.Spec.Resources.Requests.Cpu(), env.Spec.Resources.Limits.Cpu(), + env.Spec.Resources.Requests.Memory(), env.Spec.Resources.Limits.Memory(), + env.Spec.AllowAccessToExternalNetwork, env.Spec.TerminationGracePeriod) + } + fmt.Fprintf(w, "\n") + w.Flush() + } +} + +// ShowPackages displays info of Packages +func ShowPackages(pkgList []fv1.Package) { + if len(pkgList) > 0 { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + fmt.Fprintf(w, "%v\n", "Packages:") + fmt.Fprintf(w, "%v\t%v\t%v\t%v\n", "NAME", "BUILD_STATUS", "ENV", "LASTUPDATEDAT") + for _, pkg := range pkgList { + fmt.Fprintf(w, "%v\t%v\t%v\t%v\n", pkg.Metadata.Name, pkg.Status.BuildStatus, pkg.Spec.Environment.Name, pkg.Status.LastUpdateTimestamp.Format(time.RFC822)) + } + fmt.Fprintf(w, "\n") + w.Flush() + } +} + +// ShowCanaryConfigs displays info of Canary Configs +func ShowCanaryConfigs(canaryCfgs []fv1.CanaryConfig) { + if len(canaryCfgs) > 0 { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + fmt.Fprintf(w, "%v\n", "Canary Config:") + fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", "NAME", "TRIGGER", "FUNCTION-N", "FUNCTION-N-1", "WEIGHT-INCREMENT", "INTERVAL", "FAILURE-THRESHOLD", "FAILURE-TYPE", "STATUS") + for _, canaryCfg := range canaryCfgs { + fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", + canaryCfg.Metadata.Name, canaryCfg.Spec.Trigger, canaryCfg.Spec.NewFunction, canaryCfg.Spec.OldFunction, canaryCfg.Spec.WeightIncrement, canaryCfg.Spec.WeightIncrementDuration, + canaryCfg.Spec.FailureThreshold, canaryCfg.Spec.FailureType, canaryCfg.Status.Status) + } + fmt.Fprintf(w, "\n") + w.Flush() + } +} + +// ShowHTTPTriggers displays info of HTTP Triggers +func ShowHTTPTriggers(hts []fv1.HTTPTrigger) { + if len(hts) > 0 { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + fmt.Fprintf(w, "%v\n", "HTTP Triggers:") + fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", "NAME", "METHOD", "URL", "FUNCTION(s)", "INGRESS", "HOST", "PATH", "TLS", "ANNOTATIONS") + for _, trigger := range hts { + function := "" + if trigger.Spec.FunctionReference.Type == fv1.FunctionReferenceTypeFunctionName { + function = trigger.Spec.FunctionReference.Name + } else { + for k, v := range trigger.Spec.FunctionReference.FunctionWeights { + function += fmt.Sprintf("%s:%v ", k, v) + } + } + + host := trigger.Spec.Host + if len(trigger.Spec.IngressConfig.Host) > 0 { + host = trigger.Spec.IngressConfig.Host + } + path := trigger.Spec.RelativeURL + if len(trigger.Spec.IngressConfig.Path) > 0 { + path = trigger.Spec.IngressConfig.Path + } + + var msg []string + for k, v := range trigger.Spec.IngressConfig.Annotations { + msg = append(msg, fmt.Sprintf("%v: %v", k, v)) + } + ann := strings.Join(msg, ", ") + + fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", + trigger.Metadata.Name, trigger.Spec.Method, trigger.Spec.RelativeURL, function, trigger.Spec.CreateIngress, host, path, trigger.Spec.IngressConfig.TLS, ann) + } + fmt.Fprintf(w, "\n") + w.Flush() + } +} + +// ShowMQTriggers displays info of MessageQueue Triggers +func ShowMQTriggers(mqts []fv1.MessageQueueTrigger) { + if len(mqts) > 0 { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + fmt.Printf("\nMessageQueue Triggers:\n") + fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", "NAME", "FUNCTION_NAME", "MESSAGE_QUEUE_TYPE", "TOPIC", "RESPONSE_TOPIC", "ERROR_TOPIC", "MAX_RETRIES", "PUB_MSG_CONTENT_TYPE") + for _, mqt := range mqts { + fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", + mqt.Metadata.Name, mqt.Spec.FunctionReference.Name, mqt.Spec.MessageQueueType, mqt.Spec.Topic, mqt.Spec.ResponseTopic, mqt.Spec.ErrorTopic, mqt.Spec.MaxRetries, mqt.Spec.ContentType) + } + fmt.Fprintf(w, "\n") + w.Flush() + } +} + +// ShowTimeTriggers displays info of Time Triggers +func ShowTimeTriggers(tts []fv1.TimeTrigger) { + if len(tts) > 0 { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + fmt.Fprintf(w, "%v", "Time Triggers:\n") + fmt.Fprintf(w, "%v\t%v\t%v\n", "NAME", "CRON", "FUNCTION_NAME") + for _, tt := range tts { + + fmt.Fprintf(w, "%v\t%v\t%v\n", + tt.Metadata.Name, tt.Spec.Cron, tt.Spec.FunctionReference.Name) + } + fmt.Fprintf(w, "\n") + w.Flush() + } +} + +// ShowAppliedKubeWatchers displays info of kube watchers +func ShowAppliedKubeWatchers(ws []fv1.KubernetesWatchTrigger) { + if len(ws) > 0 { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + fmt.Fprintf(w, "%v", "Kube Watchers:\n") + fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\n", "NAME", "NAMESPACE", "OBJTYPE", "LABELS", "FUNCTION_NAME") + + for _, wa := range ws { + fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\n", + wa.Metadata.Name, wa.Spec.Namespace, wa.Spec.Type, wa.Spec.LabelSelector, wa.Spec.FunctionReference.Name) + } + fmt.Fprintf(w, "\n") + w.Flush() + } +} + +// getAllFunctions get lists of functions in all namespaces +func getAllFunctions(opts *ListSubCommand) ([]fv1.Function, error) { + fns, err := opts.Client().V1().Function().List("") + if err != nil { + return nil, errors.Errorf("Unable to get Functions %v", err.Error()) + } + return fns, nil +} + +// getAllEnvironments get lists of environments in all namespaces +func getAllEnvironments(opts *ListSubCommand) ([]fv1.Environment, error) { + envs, err := opts.Client().V1().Environment().List("") + if err != nil { + return nil, errors.Errorf("Unable to get Enviornmets %v", err.Error()) + } + return envs, nil +} + +// getAllPackages get lists of packages in all namespaces +func getAllPackages(opts *ListSubCommand) ([]fv1.Package, error) { + pkgList, err := opts.Client().V1().Package().List("") + if err != nil { + return nil, errors.Errorf("Unable to get Packages %v", err.Error()) + } + return pkgList, nil +} + +// getAllCanaryConfigs get lists of canary configs in all namespaces +func getAllCanaryConfigs(opts *ListSubCommand) ([]fv1.CanaryConfig, error) { + canaryCfgs, err := opts.Client().V1().CanaryConfig().List("") + if err != nil { + return nil, errors.Errorf("Unable to get Canary Configs %v", err.Error()) + } + return canaryCfgs, nil +} + +// getAllHTTPTriggers get lists of HTTP Triggers in all namespaces +func getAllHTTPTriggers(opts *ListSubCommand) ([]fv1.HTTPTrigger, error) { + hts, err := opts.Client().V1().HTTPTrigger().List("") + if err != nil { + return nil, errors.Errorf("Unable to get HTTP Triggers %v", err.Error()) + } + return hts, nil +} + +// getAllMessageQueueTriggers get lists of MessageQueue Triggers in all namespaces +func getAllMessageQueueTriggers(opts *ListSubCommand, mqttype string) ([]fv1.MessageQueueTrigger, error) { + mqts, err := opts.Client().V1().MessageQueueTrigger().List(mqttype, "") + if err != nil { + return nil, errors.Errorf("Unable to get MessageQueue Triggers %v", err.Error()) + } + return mqts, nil +} + +// getAllTimeTriggers get lists of Time Triggers in all namespaces +func getAllTimeTriggers(opts *ListSubCommand) ([]fv1.TimeTrigger, error) { + tts, err := opts.Client().V1().TimeTrigger().List("") + if err != nil { + return nil, errors.Errorf("Unable to get Time Triggers %v", err.Error()) + } + return tts, nil +} + +// getAllKubeWatchTriggers get lists of Kube Watchers in all namespaces +func getAllKubeWatchTriggers(opts *ListSubCommand) ([]fv1.KubernetesWatchTrigger, error) { + ws, err := opts.Client().V1().KubeWatcher().List("") + if err != nil { + return nil, errors.Errorf("Unable to get Kube Watchers %v", err.Error()) + } + return ws, nil +}