diff --git a/pkg/fission-cli/cli.go b/pkg/fission-cli/cli.go index be4b7f30..a9a5e037 100644 --- a/pkg/fission-cli/cli.go +++ b/pkg/fission-cli/cli.go @@ -30,6 +30,7 @@ import ( "github.com/fission/fission/pkg/fission-cli/cliwrapper/driver/urfavecli" "github.com/fission/fission/pkg/fission-cli/cmd" "github.com/fission/fission/pkg/fission-cli/cmd/environment" + "github.com/fission/fission/pkg/fission-cli/cmd/httptrigger" "github.com/fission/fission/pkg/fission-cli/cmd/kubewatch" _package "github.com/fission/fission/pkg/fission-cli/cmd/package" plugincmd "github.com/fission/fission/pkg/fission-cli/cmd/plugin" @@ -154,11 +155,11 @@ func NewCliApp() *cli.App { htFnWeightFlag := cli.IntSliceFlag{Name: "weight", Usage: "Weight for each function supplied with --function flag, in the same order. Used for canary deployment"} htFnFilterFlag := cli.StringFlag{Name: "function", Usage: "Name of the function for trigger(s)"} htSubcommands := []cli.Command{ - {Name: "create", Aliases: []string{"add"}, Usage: "Create HTTP trigger", Flags: []cli.Flag{htNameFlag, htMethodFlag, htUrlFlag, htFnNameFlag, htIngressRuleFlag, htIngressAnnotationFlag, htIngressTLSFlag, htIngressFlag, fnNamespaceFlag, specSaveFlag, htFnWeightFlag, htHostFlag}, Action: htCreate}, - {Name: "get", Usage: "Get HTTP trigger", Flags: []cli.Flag{htNameFlag}, Action: htGet}, - {Name: "update", Usage: "Update HTTP trigger", Flags: []cli.Flag{htNameFlag, triggerNamespaceFlag, htFnNameFlag, htIngressRuleFlag, htIngressAnnotationFlag, htIngressTLSFlag, htIngressFlag, htFnWeightFlag, htHostFlag}, Action: htUpdate}, - {Name: "delete", Usage: "Delete HTTP trigger", Flags: []cli.Flag{htNameFlag, triggerNamespaceFlag, htFnFilterFlag}, Action: htDelete}, - {Name: "list", Usage: "List HTTP triggers", Flags: []cli.Flag{triggerNamespaceFlag, htFnFilterFlag}, Action: htList}, + {Name: "create", Aliases: []string{"add"}, Usage: "Create HTTP trigger", Flags: []cli.Flag{htNameFlag, htMethodFlag, htUrlFlag, htFnNameFlag, htIngressRuleFlag, htIngressAnnotationFlag, htIngressTLSFlag, htIngressFlag, fnNamespaceFlag, specSaveFlag, htFnWeightFlag, htHostFlag}, Action: urfavecli.Wrapper(httptrigger.Create)}, + {Name: "get", Usage: "Get HTTP trigger", Flags: []cli.Flag{htNameFlag}, Action: urfavecli.Wrapper(httptrigger.Get)}, + {Name: "update", Usage: "Update HTTP trigger", Flags: []cli.Flag{htNameFlag, triggerNamespaceFlag, htFnNameFlag, htIngressRuleFlag, htIngressAnnotationFlag, htIngressTLSFlag, htIngressFlag, htFnWeightFlag, htHostFlag}, Action: urfavecli.Wrapper(httptrigger.Update)}, + {Name: "delete", Usage: "Delete HTTP trigger", Flags: []cli.Flag{htNameFlag, triggerNamespaceFlag, htFnFilterFlag}, Action: urfavecli.Wrapper(httptrigger.Delete)}, + {Name: "list", Usage: "List HTTP triggers", Flags: []cli.Flag{triggerNamespaceFlag, htFnFilterFlag}, Action: urfavecli.Wrapper(httptrigger.List)}, } // timetriggers diff --git a/pkg/fission-cli/cmd/httptrigger/create.go b/pkg/fission-cli/cmd/httptrigger/create.go new file mode 100644 index 00000000..550b8f77 --- /dev/null +++ b/pkg/fission-cli/cmd/httptrigger/create.go @@ -0,0 +1,220 @@ +/* +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 httptrigger + +import ( + "fmt" + "github.com/pkg/errors" + "net/http" + "strings" + + "github.com/satori/go.uuid" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + fv1 "github.com/fission/fission/pkg/apis/fission.io/v1" + "github.com/fission/fission/pkg/controller/client" + ferror "github.com/fission/fission/pkg/error" + "github.com/fission/fission/pkg/fission-cli/cliwrapper/cli" + "github.com/fission/fission/pkg/fission-cli/cmd" + "github.com/fission/fission/pkg/fission-cli/cmd/spec" + "github.com/fission/fission/pkg/fission-cli/log" + "github.com/fission/fission/pkg/fission-cli/util" +) + +type CreateSubCommand struct { + client *client.Client + trigger *fv1.HTTPTrigger +} + +func Create(flags cli.Input) error { + opts := CreateSubCommand{ + client: cmd.GetServer(flags), + } + return opts.do(flags) +} + +func (opts *CreateSubCommand) do(flags cli.Input) error { + err := opts.complete(flags) + if err != nil { + return err + } + return opts.run(flags) +} + +// complete creates a environment objects and populates it with default value and CLI inputs. +func (opts *CreateSubCommand) complete(flags cli.Input) error { + functionList := flags.StringSlice("function") + functionWeightsList := flags.IntSlice("weight") + + if len(functionList) == 0 { + return errors.New("need a function name to create a trigger, use --function") + } + + functionRef, err := setHtFunctionRef(functionList, functionWeightsList) + if err != nil { + return err + } + + triggerName := flags.String("name") + fnNamespace := flags.String("fnNamespace") + + m := &metav1.ObjectMeta{ + Name: triggerName, + Namespace: fnNamespace, + } + + htTrigger, err := opts.client.HTTPTriggerGet(m) + if err != nil && !ferror.IsNotFound(err) { + return err + } + if htTrigger != nil { + return errors.New("duplicate trigger exists, choose a different name or leave it empty for fission to auto-generate it") + } + + triggerUrl := flags.String("url") + if len(triggerUrl) == 0 { + return errors.New("need a trigger URL, use --url") + } + if !strings.HasPrefix(triggerUrl, "/") { + triggerUrl = fmt.Sprintf("/%s", triggerUrl) + } + + method, err := GetMethod(flags.String("method")) + if err != nil { + return err + } + + // For Specs, the spec validate checks for function reference + if !flags.Bool("spec") { + err = util.CheckFunctionExistence(opts.client, functionList, fnNamespace) + if err != nil { + log.Warn(err.Error()) + } + } + + createIngress := flags.Bool("createingress") + ingressConfig, err := GetIngressConfig( + flags.StringSlice("ingressannotation"), flags.String("ingressrule"), + flags.String("ingresstls"), triggerUrl, nil) + if err != nil { + return errors.Wrap(err, "error parsing ingress configuration") + } + + host := flags.String("host") + if flags.IsSet("host") { + log.Warn(fmt.Sprintf("--host is now marked as deprecated, see 'help' for details")) + } + + // just name triggers by uuid. + if triggerName == "" { + triggerName = uuid.NewV4().String() + } + + opts.trigger = &fv1.HTTPTrigger{ + Metadata: metav1.ObjectMeta{ + Name: triggerName, + Namespace: fnNamespace, + }, + Spec: fv1.HTTPTriggerSpec{ + Host: host, + RelativeURL: triggerUrl, + Method: method, + FunctionReference: *functionRef, + CreateIngress: createIngress, + IngressConfig: *ingressConfig, + }, + } + + return nil +} + +func (opts *CreateSubCommand) run(flags cli.Input) error { + // if we're writing a spec, don't call the API + if flags.Bool("spec") { + specFile := fmt.Sprintf("route-%v.yaml", opts.trigger.Metadata.Name) + err := spec.SpecSave(*opts.trigger, specFile) + if err != nil { + return errors.Wrap(err, "error creating HTTP trigger spec") + } + return nil + } + + _, err := opts.client.HTTPTriggerCreate(opts.trigger) + if err != nil { + return errors.Wrap(err, "create HTTP trigger") + } + + fmt.Printf("trigger '%v' created\n", opts.trigger.Metadata.Name) + + return nil +} + +// GetMethod returns one of HTTP method +func GetMethod(method string) (string, error) { + switch strings.ToUpper(method) { + case "GET": + return http.MethodGet, nil + case "HEAD": + return http.MethodHead, nil + case "POST": + return http.MethodPost, nil + case "PUT": + return http.MethodPut, nil + case "PATCH": + return http.MethodPatch, nil + case "DELETE": + return http.MethodDelete, nil + case "CONNECT": + return http.MethodConnect, nil + case "OPTIONS": + return http.MethodOptions, nil + case "TRACE": + return http.MethodTrace, nil + default: + return "", fmt.Errorf("invalid or unsupported HTTP Method %v", method) + } +} + +func setHtFunctionRef(functionList []string, functionWeightsList []int) (*fv1.FunctionReference, error) { + if len(functionList) == 1 { + return &fv1.FunctionReference{ + Type: fv1.FunctionReferenceTypeFunctionName, + Name: functionList[0], + }, nil + } else if len(functionList) == 2 { + if len(functionWeightsList) != 2 { + return nil, fmt.Errorf("weights of the function need to be specified when 2 functions are supplied") + } + + totalWeight := functionWeightsList[0] + functionWeightsList[1] + if totalWeight != 100 { + return nil, errors.New("the function weights should add up to 100") + } + + functionWeights := make(map[string]int) + for index := range functionList { + functionWeights[functionList[index]] = functionWeightsList[index] + } + + return &fv1.FunctionReference{ + Type: fv1.FunctionReferenceTypeFunctionWeights, + FunctionWeights: functionWeights, + }, nil + } + + return nil, fmt.Errorf("the number of functions in a trigger can be 1 or 2(for canary feature along with their weights)") +} diff --git a/pkg/fission-cli/cmd/httptrigger/delete.go b/pkg/fission-cli/cmd/httptrigger/delete.go new file mode 100644 index 00000000..0a926b27 --- /dev/null +++ b/pkg/fission-cli/cmd/httptrigger/delete.go @@ -0,0 +1,104 @@ +/* +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 httptrigger + +import ( + "fmt" + + "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/fission/fission/pkg/controller/client" + "github.com/fission/fission/pkg/fission-cli/cliwrapper/cli" + "github.com/fission/fission/pkg/fission-cli/cmd" +) + +type DeleteSubCommand struct { + client *client.Client + triggerName string + functionName string + namespace string +} + +func Delete(flags cli.Input) error { + opts := DeleteSubCommand{ + client: cmd.GetServer(flags), + } + return opts.do(flags) +} + +func (opts *DeleteSubCommand) do(flags cli.Input) error { + err := opts.complete(flags) + if err != nil { + return err + } + return opts.run(flags) +} + +// complete creates a environment objects and populates it with default value and CLI inputs. +func (opts *DeleteSubCommand) complete(flags cli.Input) error { + opts.triggerName = flags.String("name") + opts.functionName = flags.String("function") + if len(opts.triggerName) == 0 && len(opts.functionName) == 0 { + return errors.New("need --name or --function") + } else if len(opts.triggerName) > 0 && len(opts.functionName) > 0 { + return errors.New("need either of --name or --function and not both arguments") + } + opts.namespace = flags.String("triggerNamespace") + return nil +} + +func (opts *DeleteSubCommand) run(flags cli.Input) error { + triggers, err := opts.client.HTTPTriggerList(opts.namespace) + if err != nil { + return errors.Wrap(err, "error getting HTTP trigger list") + } + + var triggersToDelete []string + + if len(opts.functionName) > 0 { + for _, trigger := range triggers { + // TODO: delete canary http triggers as well. + if trigger.Spec.FunctionReference.Name == opts.functionName { + triggersToDelete = append(triggersToDelete, trigger.Metadata.Name) + } + } + } else { + triggersToDelete = []string{opts.triggerName} + } + + errs := &multierror.Error{} + + for _, name := range triggersToDelete { + err := opts.client.HTTPTriggerDelete(&metav1.ObjectMeta{ + Name: name, + Namespace: opts.namespace, + }) + if err != nil { + errs = multierror.Append(errs, err) + } else { + fmt.Printf("trigger '%v' deleted\n", name) + } + } + + if errs.ErrorOrNil() != nil { + return errors.Wrap(errs.ErrorOrNil(), "error deleting trigger(s)") + } + + return nil +} diff --git a/pkg/fission-cli/cmd/httptrigger/get.go b/pkg/fission-cli/cmd/httptrigger/get.go new file mode 100644 index 00000000..d6bb9684 --- /dev/null +++ b/pkg/fission-cli/cmd/httptrigger/get.go @@ -0,0 +1,113 @@ +/* +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 httptrigger + +import ( + "fmt" + "os" + "strings" + "text/tabwriter" + + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + fv1 "github.com/fission/fission/pkg/apis/fission.io/v1" + "github.com/fission/fission/pkg/controller/client" + "github.com/fission/fission/pkg/fission-cli/cliwrapper/cli" + "github.com/fission/fission/pkg/fission-cli/cmd" +) + +type GetSubCommand struct { + client *client.Client + trigger string + namespace string +} + +func Get(flags cli.Input) error { + opts := GetSubCommand{ + client: cmd.GetServer(flags), + } + return opts.do(flags) +} + +func (opts *GetSubCommand) do(flags cli.Input) error { + err := opts.complete(flags) + if err != nil { + return err + } + return opts.run(flags) +} + +// complete creates a environment objects and populates it with default value and CLI inputs. +func (opts *GetSubCommand) complete(flags cli.Input) error { + opts.trigger = flags.String("name") + opts.namespace = flags.String("fnNamespace") + + if len(opts.trigger) <= 0 { + return errors.New("need a trigger name, use --name") + } + return nil +} + +func (opts *GetSubCommand) run(flags cli.Input) error { + m := &metav1.ObjectMeta{ + Name: opts.trigger, + Namespace: opts.namespace, + } + ht, err := opts.client.HTTPTriggerGet(m) + if err != nil { + return errors.Wrap(err, "error getting http trigger") + } + + printHtSummary([]fv1.HTTPTrigger{*ht}) + + return nil +} + +func printHtSummary(triggers []fv1.HTTPTrigger) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + 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 triggers { + 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) + } + w.Flush() +} diff --git a/pkg/fission-cli/cmd/httptrigger/list.go b/pkg/fission-cli/cmd/httptrigger/list.go new file mode 100644 index 00000000..bbe1a85f --- /dev/null +++ b/pkg/fission-cli/cmd/httptrigger/list.go @@ -0,0 +1,74 @@ +/* +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 httptrigger + +import ( + "github.com/pkg/errors" + + fv1 "github.com/fission/fission/pkg/apis/fission.io/v1" + "github.com/fission/fission/pkg/controller/client" + "github.com/fission/fission/pkg/fission-cli/cliwrapper/cli" + "github.com/fission/fission/pkg/fission-cli/cmd" +) + +type ListSubCommand struct { + client *client.Client + triggerNamespace string + filterFunctionName string +} + +func List(flags cli.Input) error { + opts := ListSubCommand{ + client: cmd.GetServer(flags), + } + return opts.do(flags) +} + +func (opts *ListSubCommand) do(flags cli.Input) error { + err := opts.complete(flags) + if err != nil { + return err + } + return opts.run(flags) +} + +// complete creates a environment objects and populates it with default value and CLI inputs. +func (opts *ListSubCommand) complete(flags cli.Input) error { + opts.triggerNamespace = flags.String("triggerNamespace") + opts.filterFunctionName = flags.String("function") + return nil +} + +func (opts *ListSubCommand) run(flags cli.Input) error { + hts, err := opts.client.HTTPTriggerList(opts.triggerNamespace) + if err != nil { + return errors.Wrap(err, "error listing HTTP triggers") + } + + var triggers []fv1.HTTPTrigger + for _, ht := range hts { + // TODO: list canary http triggers as well. + if len(opts.filterFunctionName) == 0 || + (len(opts.filterFunctionName) > 0 && opts.filterFunctionName == ht.Spec.FunctionReference.Name) { + + triggers = append(triggers, ht) + } + } + + printHtSummary(triggers) + return nil +} diff --git a/pkg/fission-cli/cmd/httptrigger/update.go b/pkg/fission-cli/cmd/httptrigger/update.go new file mode 100644 index 00000000..af14b2d6 --- /dev/null +++ b/pkg/fission-cli/cmd/httptrigger/update.go @@ -0,0 +1,122 @@ +/* +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 httptrigger + +import ( + "fmt" + + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + fv1 "github.com/fission/fission/pkg/apis/fission.io/v1" + "github.com/fission/fission/pkg/controller/client" + "github.com/fission/fission/pkg/fission-cli/cliwrapper/cli" + "github.com/fission/fission/pkg/fission-cli/cmd" + "github.com/fission/fission/pkg/fission-cli/log" + "github.com/fission/fission/pkg/fission-cli/util" +) + +type UpdateSubCommand struct { + client *client.Client + trigger *fv1.HTTPTrigger +} + +func Update(flags cli.Input) error { + opts := UpdateSubCommand{ + client: cmd.GetServer(flags), + } + return opts.do(flags) +} + +func (opts *UpdateSubCommand) do(flags cli.Input) error { + err := opts.complete(flags) + if err != nil { + return err + } + return opts.run(flags) +} + +// complete creates a environment objects and populates it with default value and CLI inputs. +func (opts *UpdateSubCommand) complete(flags cli.Input) error { + htName := flags.String("name") + if len(htName) == 0 { + return errors.New("need name of trigger, use --name") + } + triggerNamespace := flags.String("triggerNamespace") + + ht, err := opts.client.HTTPTriggerGet(&metav1.ObjectMeta{ + Name: htName, + Namespace: triggerNamespace, + }) + if err != nil { + return errors.Wrap(err, "error getting HTTP trigger") + } + + if flags.IsSet("function") { + // get the functions and their weights if specified + functionList := flags.StringSlice("function") + err := util.CheckFunctionExistence(opts.client, functionList, triggerNamespace) + if err != nil { + log.Warn(err.Error()) + } + + var functionWeightsList []int + if flags.IsSet("weight") { + functionWeightsList = flags.IntSlice("weight") + } + + // set function reference + functionRef, err := setHtFunctionRef(functionList, functionWeightsList) + if err != nil { + return errors.Wrap(err, "error setting function weight") + } + + ht.Spec.FunctionReference = *functionRef + } + + if flags.IsSet("createingress") { + ht.Spec.CreateIngress = flags.Bool("createingress") + } + + if flags.IsSet("host") { + ht.Spec.Host = flags.String("host") + log.Warn(fmt.Sprintf("--host is now marked as deprecated, see 'help' for details")) + } + + if flags.IsSet("ingressrule") || flags.IsSet("ingressannotation") || flags.IsSet("ingresstls") { + ingress, err := GetIngressConfig( + flags.StringSlice("ingressannotation"), flags.String("ingressrule"), + flags.String("ingresstls"), ht.Spec.RelativeURL, &ht.Spec.IngressConfig) + if err != nil { + return errors.Wrap(err, "parse ingress configuration") + } + ht.Spec.IngressConfig = *ingress + } + + opts.trigger = ht + + return nil +} + +func (opts *UpdateSubCommand) run(flags cli.Input) error { + _, err := opts.client.HTTPTriggerUpdate(opts.trigger) + if err != nil { + return errors.Wrap(err, "error updating the HTTP trigger") + } + fmt.Printf("trigger '%v' updated\n", opts.trigger.Metadata.Name) + return nil +} diff --git a/pkg/fission-cli/function.go b/pkg/fission-cli/function.go index 23fb16b9..adb90c06 100644 --- a/pkg/fission-cli/function.go +++ b/pkg/fission-cli/function.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "github.com/fission/fission/pkg/fission-cli/cmd/httptrigger" "io/ioutil" "net/http" "net/url" @@ -397,10 +398,11 @@ func fnCreate(c *cli.Context) error { triggerUrl = fmt.Sprintf("/%s", triggerUrl) } - method := c.String("method") - if len(method) == 0 { - method = http.MethodGet + method, err := httptrigger.GetMethod(c.String("method")) + if err != nil { + util.CheckErr(err, "get HTTP trigger method") } + triggerName := uuid.NewV4().String() ht := &fv1.HTTPTrigger{ Metadata: metav1.ObjectMeta{ @@ -409,7 +411,7 @@ func fnCreate(c *cli.Context) error { }, Spec: fv1.HTTPTriggerSpec{ RelativeURL: triggerUrl, - Method: getMethod(method), + Method: method, FunctionReference: fv1.FunctionReference{ Type: fv1.FunctionReferenceTypeFunctionName, Name: fnName, diff --git a/pkg/fission-cli/httptrigger.go b/pkg/fission-cli/httptrigger.go deleted file mode 100644 index fc52239f..00000000 --- a/pkg/fission-cli/httptrigger.go +++ /dev/null @@ -1,376 +0,0 @@ -/* -Copyright 2016 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 fission_cli - -import ( - "fmt" - "net/http" - "os" - "strings" - "text/tabwriter" - - "github.com/hashicorp/go-multierror" - "github.com/satori/go.uuid" - "github.com/urfave/cli" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - fv1 "github.com/fission/fission/pkg/apis/fission.io/v1" - ferror "github.com/fission/fission/pkg/error" - "github.com/fission/fission/pkg/fission-cli/cmd/httptrigger" - "github.com/fission/fission/pkg/fission-cli/cmd/spec" - "github.com/fission/fission/pkg/fission-cli/log" - "github.com/fission/fission/pkg/fission-cli/util" -) - -// returns one of http.Method* -func getMethod(method string) string { - switch strings.ToUpper(method) { - case "GET": - return http.MethodGet - case "HEAD": - return http.MethodHead - case "POST": - return http.MethodPost - case "PUT": - return http.MethodPut - case "PATCH": - return http.MethodPatch - case "DELETE": - return http.MethodDelete - case "CONNECT": - return http.MethodConnect - case "OPTIONS": - return http.MethodOptions - case "TRACE": - return http.MethodTrace - } - log.Fatal(fmt.Sprintf("Invalid HTTP Method %v", method)) - return "" -} - -func setHtFunctionRef(functionList []string, functionWeightsList []int) (*fv1.FunctionReference, error) { - if len(functionList) == 1 { - return &fv1.FunctionReference{ - Type: fv1.FunctionReferenceTypeFunctionName, - Name: functionList[0], - }, nil - } else if len(functionList) == 2 { - if len(functionWeightsList) != 2 { - return nil, fmt.Errorf("weights of the function need to be specified when 2 functions are supplied") - } - - totalWeight := functionWeightsList[0] + functionWeightsList[1] - if totalWeight != 100 { - log.Fatal("The function weights should add up to 100") - } - - functionWeights := make(map[string]int) - for index := range functionList { - functionWeights[functionList[index]] = functionWeightsList[index] - } - - return &fv1.FunctionReference{ - Type: fv1.FunctionReferenceTypeFunctionWeights, - FunctionWeights: functionWeights, - }, nil - } - - return nil, fmt.Errorf("the number of functions in a trigger can be 1 or 2(for canary feature along with their weights)") -} - -func htCreate(c *cli.Context) error { - client := util.GetApiClient(c.GlobalString("server")) - - functionList := c.StringSlice("function") - functionWeightsList := c.IntSlice("weight") - - if len(functionList) == 0 { - log.Fatal("Need a function name to create a trigger, use --function") - } - - functionRef, err := setHtFunctionRef(functionList, functionWeightsList) - if err != nil { - log.Fatal(err.Error()) - } - - triggerName := c.String("name") - fnNamespace := c.String("fnNamespace") - toSpec := c.Bool("spec") - - m := &metav1.ObjectMeta{ - Name: triggerName, - Namespace: fnNamespace, - } - - htTrigger, err := client.HTTPTriggerGet(m) - if err != nil && !ferror.IsNotFound(err) { - log.Fatal(err.Error()) - } - if htTrigger != nil { - util.CheckErr(fmt.Errorf("duplicate trigger exists"), "choose a different name or leave it empty for fission to auto-generate it") - } - - triggerUrl := c.String("url") - if len(triggerUrl) == 0 { - log.Fatal("Need a trigger URL, use --url") - } - if !strings.HasPrefix(triggerUrl, "/") { - triggerUrl = fmt.Sprintf("/%s", triggerUrl) - } - - method := c.String("method") - if len(method) == 0 { - method = "GET" - } - - // For Specs, the spec validate checks for function reference - if !toSpec { - err = util.CheckFunctionExistence(client, functionList, fnNamespace) - if err != nil { - log.Warn(err.Error()) - } - } - - createIngress := c.Bool("createingress") - ingressConfig, err := httptrigger.GetIngressConfig( - c.StringSlice("ingressannotation"), c.String("ingressrule"), - c.String("ingresstls"), triggerUrl, nil) - util.CheckErr(err, "parse ingress configuration") - - host := c.String("host") - if c.IsSet("host") { - log.Warn(fmt.Sprintf("--host is now marked as deprecated, see 'help' for details")) - } - - // just name triggers by uuid. - if triggerName == "" { - triggerName = uuid.NewV4().String() - } - - ht := &fv1.HTTPTrigger{ - Metadata: metav1.ObjectMeta{ - Name: triggerName, - Namespace: fnNamespace, - }, - Spec: fv1.HTTPTriggerSpec{ - Host: host, - RelativeURL: triggerUrl, - Method: getMethod(method), - FunctionReference: *functionRef, - CreateIngress: createIngress, - IngressConfig: *ingressConfig, - }, - } - - // if we're writing a spec, don't call the API - if toSpec { - specFile := fmt.Sprintf("route-%v.yaml", triggerName) - err := spec.SpecSave(*ht, specFile) - util.CheckErr(err, "create HTTP trigger spec") - return nil - } - - _, err = client.HTTPTriggerCreate(ht) - util.CheckErr(err, "create HTTP trigger") - - fmt.Printf("trigger '%v' created\n", triggerName) - return err -} - -func htGet(c *cli.Context) error { - cliClient := util.GetApiClient(c.GlobalString("server")) - - name := c.String("name") - ns := c.String("fnNamespace") - - if len(name) <= 0 { - log.Fatal("Need a trigger name, use --name") - } - - m := &metav1.ObjectMeta{ - Name: name, - Namespace: ns, - } - ht, err := cliClient.HTTPTriggerGet(m) - util.CheckErr(err, "get http trigger") - - printHtSummary([]fv1.HTTPTrigger{*ht}) - return err -} - -func htUpdate(c *cli.Context) error { - client := util.GetApiClient(c.GlobalString("server")) - htName := c.String("name") - if len(htName) == 0 { - log.Fatal("Need name of trigger, use --name") - } - triggerNamespace := c.String("triggerNamespace") - - ht, err := client.HTTPTriggerGet(&metav1.ObjectMeta{ - Name: htName, - Namespace: triggerNamespace, - }) - util.CheckErr(err, "get HTTP trigger") - - if c.IsSet("function") { - // get the functions and their weights if specified - functionList := c.StringSlice("function") - err := util.CheckFunctionExistence(client, functionList, triggerNamespace) - if err != nil { - if err != nil { - log.Warn(err.Error()) - } - } - - var functionWeightsList []int - if c.IsSet("weight") { - functionWeightsList = c.IntSlice("weight") - } - - // set function reference - functionRef, err := setHtFunctionRef(functionList, functionWeightsList) - if err != nil { - log.Fatal(err.Error()) - } - - ht.Spec.FunctionReference = *functionRef - } - - if c.IsSet("createingress") { - ht.Spec.CreateIngress = c.Bool("createingress") - } - - if c.IsSet("host") { - ht.Spec.Host = c.String("host") - log.Warn(fmt.Sprintf("--host is now marked as deprecated, see 'help' for details")) - } - - if c.IsSet("ingressrule") || c.IsSet("ingressannotation") || c.IsSet("ingresstls") { - _, err = httptrigger.GetIngressConfig( - c.StringSlice("ingressannotation"), c.String("ingressrule"), - c.String("ingresstls"), ht.Spec.RelativeURL, &ht.Spec.IngressConfig) - util.CheckErr(err, "parse ingress configuration") - } - - _, err = client.HTTPTriggerUpdate(ht) - util.CheckErr(err, "update HTTP trigger") - - fmt.Printf("trigger '%v' updated\n", htName) - return nil -} - -func htDelete(c *cli.Context) error { - client := util.GetApiClient(c.GlobalString("server")) - - htName := c.String("name") - fnName := c.String("function") - if len(htName) == 0 && len(fnName) == 0 { - log.Fatal("Need --name or --function") - } else if len(htName) > 0 && len(fnName) > 0 { - log.Fatal("Need either of --name or --function and not both arguments") - } - - triggerNamespace := c.String("triggerNamespace") - - triggers, err := client.HTTPTriggerList(triggerNamespace) - util.CheckErr(err, "get HTTP trigger list") - - var triggersToDelete []string - - if len(fnName) > 0 { - for _, trigger := range triggers { - // TODO: delete canary http triggers as well. - if trigger.Spec.FunctionReference.Name == fnName { - triggersToDelete = append(triggersToDelete, trigger.Metadata.Name) - } - } - } else { - triggersToDelete = []string{htName} - } - - errs := &multierror.Error{} - - for _, name := range triggersToDelete { - err := client.HTTPTriggerDelete(&metav1.ObjectMeta{ - Name: name, - Namespace: triggerNamespace, - }) - if err != nil { - errs = multierror.Append(errs, err) - } else { - fmt.Printf("trigger '%v' deleted\n", name) - } - } - - util.CheckErr(errs.ErrorOrNil(), "delete trigger(s)") - - return nil -} - -func htList(c *cli.Context) error { - client := util.GetApiClient(c.GlobalString("server")) - triggerNamespace := c.String("triggerNamespace") - fnName := c.String("function") - - hts, err := client.HTTPTriggerList(triggerNamespace) - util.CheckErr(err, "list HTTP triggers") - - var triggers []fv1.HTTPTrigger - for _, ht := range hts { - // TODO: list canary http triggers as well. - if len(fnName) == 0 || (len(fnName) > 0 && fnName == ht.Spec.FunctionReference.Name) { - triggers = append(triggers, ht) - } - } - - printHtSummary(triggers) - return nil -} - -func printHtSummary(triggers []fv1.HTTPTrigger) { - w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) - 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 triggers { - 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) - } - w.Flush() -}