From 745bbb420d9ca5f12b042376b7b5557753c61378 Mon Sep 17 00:00:00 2001 From: Paul Lorenz Date: Fri, 29 May 2020 16:10:50 -0400 Subject: [PATCH 1/2] Add identity service config CLI commands. Add update terminator CLI command. Fixes GH-105. Fixes GH-106 --- doc/release-notes.md | 9 ++ ziti/cmd/ziti/cmd/edge_controller/common.go | 5 +- .../cmd/edge_controller/create_terminator.go | 10 +- ziti/cmd/ziti/cmd/edge_controller/list.go | 20 +++ ziti/cmd/ziti/cmd/edge_controller/update.go | 18 ++- .../update_identity_configs.go | 96 +++++++++++++ .../cmd/edge_controller/update_terminator.go | 126 ++++++++++++++++++ ziti/cmd/ziti/util/rest.go | 19 +-- 8 files changed, 278 insertions(+), 25 deletions(-) create mode 100644 ziti/cmd/ziti/cmd/edge_controller/update_identity_configs.go create mode 100644 ziti/cmd/ziti/cmd/edge_controller/update_terminator.go diff --git a/doc/release-notes.md b/doc/release-notes.md index 46b48f997..f0209da85 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,3 +1,12 @@ +# Release 0.14.7 +## Theme +Ziti 0.14.7 includes the following: + +* [Add CLI support for updating terminators](https://github.com/openziti/ziti/issues/106) +* [Add CLI support for managing identity service config overrides](https://github.com/openziti/ziti/issues/105) + +NOTE: 0.14.6 was released with the same code as 0.14.5 due to CI re-running + # Release 0.14.5 ## Theme Ziti 0.14.5 includes the following: diff --git a/ziti/cmd/ziti/cmd/edge_controller/common.go b/ziti/cmd/ziti/cmd/edge_controller/common.go index dcabc2ff0..d2f3cdb28 100644 --- a/ziti/cmd/ziti/cmd/edge_controller/common.go +++ b/ziti/cmd/ziti/cmd/edge_controller/common.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/google/uuid" "github.com/pkg/errors" + "os" "strings" ) @@ -48,7 +49,9 @@ func mapNameToID(entityType string, val string) (string, error) { entity := list[0] entityId, _ := entity.Path("id").Data().(string) - fmt.Printf("Found %v with id %v for name %v\n", entityType, entityId, val) + if val, found := os.LookupEnv("DEBUG"); found && strings.EqualFold("true", val) { + fmt.Printf("Found %v with id %v for name %v\n", entityType, entityId, val) + } return entityId, nil } diff --git a/ziti/cmd/ziti/cmd/edge_controller/create_terminator.go b/ziti/cmd/ziti/cmd/edge_controller/create_terminator.go index 726b33705..a1843724f 100644 --- a/ziti/cmd/ziti/cmd/edge_controller/create_terminator.go +++ b/ziti/cmd/ziti/cmd/edge_controller/create_terminator.go @@ -19,10 +19,10 @@ package edge_controller import ( "fmt" "github.com/Jeffail/gabs" + "github.com/openziti/foundation/util/stringz" "github.com/openziti/ziti/ziti/cmd/ziti/cmd/common" cmdutil "github.com/openziti/ziti/ziti/cmd/ziti/cmd/factory" cmdhelper "github.com/openziti/ziti/ziti/cmd/ziti/cmd/helpers" - "github.com/openziti/foundation/util/stringz" "github.com/spf13/cobra" "io" "math" @@ -33,7 +33,6 @@ type createTerminatorOptions struct { binding string cost int32 precedence string - tags map[string]string } // newCreateTerminatorCmd creates the 'edge controller create Terminator local' command for the given entity type @@ -46,7 +45,6 @@ func newCreateTerminatorCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) Err: errOut, }, }, - tags: make(map[string]string), } cmd := &cobra.Command{ @@ -65,11 +63,10 @@ func newCreateTerminatorCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) // allow interspersing positional args and flags cmd.Flags().SetInterspersed(true) - cmd.Flags().StringToStringVarP(&options.tags, "tags", "t", nil, "Add tags to terminator definition") cmd.Flags().BoolVarP(&options.OutputJSONResponse, "output-json", "j", false, "Output the full JSON response from the Ziti Edge Controller") cmd.Flags().StringVar(&options.binding, "binding", "transport", "Set the terminator binding") - cmd.Flags().Int32VarP(&options.cost, "cost", "c", -1, "Set the terminator cost") - cmd.Flags().StringVarP(&options.precedence, "precedence", "p", "", "Set the terminator precedence ('default', 'required' or 'failed'") + cmd.Flags().Int32VarP(&options.cost, "cost", "c", 0, "Set the terminator cost") + cmd.Flags().StringVarP(&options.precedence, "precedence", "p", "", "Set the terminator precedence ('default', 'required' or 'failed')") return cmd } @@ -91,7 +88,6 @@ func runCreateTerminator(o *createTerminatorOptions) (err error) { setJSONValue(entityData, router, "router") setJSONValue(entityData, o.binding, "binding") setJSONValue(entityData, o.Args[2], "address") - setJSONValue(entityData, o.tags, "tags") if o.cost > 0 { if o.cost > math.MaxUint16 { if _, err = fmt.Fprintf(o.Out, "Invalid cost %v. Must be positive number less than or equal to %v\n", o.cost, math.MaxUint16); err != nil { diff --git a/ziti/cmd/ziti/cmd/edge_controller/list.go b/ziti/cmd/ziti/cmd/edge_controller/list.go index 6e9cfbde3..6be55feba 100644 --- a/ziti/cmd/ziti/cmd/edge_controller/list.go +++ b/ziti/cmd/ziti/cmd/edge_controller/list.go @@ -92,6 +92,7 @@ func newListCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Comma identityListRootCmd.AddCommand(newSubListCmdForEntityType("identities", "edge-routers", outputEdgeRouters, newOptions())) identityListRootCmd.AddCommand(newSubListCmdForEntityType("identities", "service-policies", outputServicePolicies, newOptions())) identityListRootCmd.AddCommand(newSubListCmdForEntityType("identities", "services", outputServices, newOptions())) + identityListRootCmd.AddCommand(newSubListCmdForEntityType("identities", "service-configs", outputServiceConfigs, newOptions())) serviceListRootCmd := newEntityListRootCmd("service") serviceListRootCmd.AddCommand(newSubListCmdForEntityType("services", "configs", outputConfigs, newOptions())) @@ -510,6 +511,25 @@ func outputServices(o *commonOptions, children []*gabs.Container, pagingInfo *pa return nil } +func outputServiceConfigs(o *commonOptions, children []*gabs.Container, pagingInfo *paging) error { + if o.OutputJSONResponse { + return nil + } + + for _, entity := range children { + service, _ := entity.Path("service").Data().(string) + serviceName, _ := mapIdToName("services", service) + config, _ := entity.Path("config").Data().(string) + configName, _ := mapIdToName("configs", config) + _, err := fmt.Fprintf(o.Out, "service: %v config: %v\n", serviceName, configName) + if err != nil { + return err + } + } + pagingInfo.output(o) + return nil +} + func runListServiceEdgeRouterPolices(o *commonOptions) error { children, pagingInfo, err := listEntitiesWithOptions("service-edge-router-policies", o) if err != nil { diff --git a/ziti/cmd/ziti/cmd/edge_controller/update.go b/ziti/cmd/ziti/cmd/edge_controller/update.go index d86c6b043..e92eefb4c 100644 --- a/ziti/cmd/ziti/cmd/edge_controller/update.go +++ b/ziti/cmd/ziti/cmd/edge_controller/update.go @@ -44,24 +44,34 @@ func newUpdateCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Com cmd.AddCommand(newUpdateEdgeRouterCmd(f, out, errOut)) cmd.AddCommand(newUpdateEdgeRouterPolicyCmd(f, out, errOut)) cmd.AddCommand(newUpdateIdentityCmd(f, out, errOut)) + cmd.AddCommand(newUpdateIdentityConfigsCmd(f, out, errOut)) cmd.AddCommand(newUpdateServiceCmd(f, out, errOut)) cmd.AddCommand(newUpdateServicePolicyCmd(f, out, errOut)) cmd.AddCommand(newUpdateServiceEdgeRouterPolicyCmd(f, out, errOut)) + cmd.AddCommand(newUpdateTerminatorCmd(f, out, errOut)) return cmd } func putEntityOfType(entityType string, body string, options *commonOptions) (*gabs.Container, error) { - return updateEntityOfType(entityType, body, options, true) + return updateEntityOfType(entityType, body, options, resty.MethodPut) } func patchEntityOfType(entityType string, body string, options *commonOptions) (*gabs.Container, error) { - return updateEntityOfType(entityType, body, options, false) + return updateEntityOfType(entityType, body, options, resty.MethodPatch) +} + +func postEntityOfType(entityType string, body string, options *commonOptions) (*gabs.Container, error) { + return updateEntityOfType(entityType, body, options, resty.MethodPost) +} + +func deleteEntityOfTypeWithBody(entityType string, body string, options *commonOptions) (*gabs.Container, error) { + return updateEntityOfType(entityType, body, options, resty.MethodDelete) } // updateEntityOfType updates an entity of the given type on the Ziti Edge Controller -func updateEntityOfType(entityType string, body string, options *commonOptions, put bool) (*gabs.Container, error) { - return util.EdgeControllerUpdate(entityType, body, options.Out, put, options.OutputJSONResponse) +func updateEntityOfType(entityType string, body string, options *commonOptions, method string) (*gabs.Container, error) { + return util.EdgeControllerUpdate(entityType, body, options.Out, method, options.OutputJSONResponse) } func doRequest(entityType string, options *commonOptions, doRequest func(request *resty.Request, url string) (*resty.Response, error)) (*gabs.Container, error) { diff --git a/ziti/cmd/ziti/cmd/edge_controller/update_identity_configs.go b/ziti/cmd/ziti/cmd/edge_controller/update_identity_configs.go new file mode 100644 index 000000000..d8a492f09 --- /dev/null +++ b/ziti/cmd/ziti/cmd/edge_controller/update_identity_configs.go @@ -0,0 +1,96 @@ +/* + Copyright NetFoundry, Inc. + + 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 + + https://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 edge_controller + +import ( + "fmt" + "io" + + "github.com/Jeffail/gabs" + "github.com/openziti/ziti/ziti/cmd/ziti/cmd/common" + cmdutil "github.com/openziti/ziti/ziti/cmd/ziti/cmd/factory" + cmdhelper "github.com/openziti/ziti/ziti/cmd/ziti/cmd/helpers" + "github.com/spf13/cobra" +) + +type updateIdentityConfigsOptions struct { + commonOptions + remove bool +} + +func newUpdateIdentityConfigsCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command { + options := &updateIdentityConfigsOptions{ + commonOptions: commonOptions{ + CommonOptions: common.CommonOptions{Factory: f, Out: out, Err: errOut}, + }, + } + + cmd := &cobra.Command{ + Use: "identity-configs ", + Short: "for the specified identity, use the given config for the given service", + Args: cobra.ExactArgs(3), + Run: func(cmd *cobra.Command, args []string) { + options.Cmd = cmd + options.Args = args + err := runupdateIdentityConfigs(options) + cmdhelper.CheckErr(err) + }, + SuggestFor: []string{}, + } + + // allow interspersing positional args and flags + cmd.Flags().SetInterspersed(true) + cmd.Flags().BoolVarP(&options.OutputJSONResponse, "output-json", "j", false, "Output the full JSON response from the Ziti Edge Controller") + cmd.Flags().BoolVarP(&options.remove, "remove", "r", false, "Remove the sevice config override") + + return cmd +} + +// runupdateIdentityConfigs update a new identity on the Ziti Edge Controller +func runupdateIdentityConfigs(o *updateIdentityConfigsOptions) error { + id, err := mapNameToID("identities", o.Args[0]) + if err != nil { + return err + } + + serviceId, err := mapNameToID("services", o.Args[1]) + if err != nil { + return err + } + + configId, err := mapNameToID("configs", o.Args[2]) + if err != nil { + return err + } + + entityData, _ := gabs.New().ArrayOfSize(1) + serviceConfig := map[string]string{ + "service": serviceId, + "config": configId, + } + _, _ = entityData.SetIndex(serviceConfig, 0) + + path := fmt.Sprintf("identities/%v/service-configs", id) + body := entityData.String() + + if o.remove { + _, err = deleteEntityOfTypeWithBody(path, body, &o.commonOptions) + } else { + _, err = postEntityOfType(path, body, &o.commonOptions) + } + return err +} diff --git a/ziti/cmd/ziti/cmd/edge_controller/update_terminator.go b/ziti/cmd/ziti/cmd/edge_controller/update_terminator.go new file mode 100644 index 000000000..bb859598f --- /dev/null +++ b/ziti/cmd/ziti/cmd/edge_controller/update_terminator.go @@ -0,0 +1,126 @@ +/* + Copyright NetFoundry, Inc. + + 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 + + https://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 edge_controller + +import ( + "errors" + "fmt" + "github.com/Jeffail/gabs" + "github.com/openziti/foundation/util/stringz" + "github.com/openziti/ziti/ziti/cmd/ziti/cmd/common" + cmdutil "github.com/openziti/ziti/ziti/cmd/ziti/cmd/factory" + cmdhelper "github.com/openziti/ziti/ziti/cmd/ziti/cmd/helpers" + errors2 "github.com/pkg/errors" + "github.com/spf13/cobra" + "io" + "math" +) + +type updateTerminatorOptions struct { + commonOptions + router string + address string + binding string + cost int32 + precedence string +} + +func newUpdateTerminatorCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command { + options := &updateTerminatorOptions{ + commonOptions: commonOptions{ + CommonOptions: common.CommonOptions{ + Factory: f, + Out: out, + Err: errOut, + }, + }, + } + + cmd := &cobra.Command{ + Use: "terminator ", + Short: "updates a service terminator", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + options.Cmd = cmd + options.Args = args + err := runUpdateTerminator(options) + cmdhelper.CheckErr(err) + }, + SuggestFor: []string{}, + } + + // allow interspersing positional args and flags + cmd.Flags().SetInterspersed(true) + cmd.Flags().BoolVarP(&options.OutputJSONResponse, "output-json", "j", false, "Output the full JSON response from the Ziti Edge Controller") + cmd.Flags().StringVar(&options.router, "router", "", "Set the terminator router") + cmd.Flags().StringVar(&options.address, "address", "", "Set the terminator address") + cmd.Flags().StringVar(&options.binding, "binding", "", "Set the terminator binding") + cmd.Flags().Int32VarP(&options.cost, "cost", "c", 0, "Set the terminator cost") + cmd.Flags().StringVarP(&options.precedence, "precedence", "p", "", "Set the terminator precedence ('default', 'required' or 'failed')") + + return cmd +} + +// runUpdateTerminator implements the command to update a Terminator +func runUpdateTerminator(o *updateTerminatorOptions) (err error) { + entityData := gabs.New() + + router, err := mapNameToID("edge-routers", o.router) + if err != nil { + router = o.router // might be a pure fabric router, id might not be UUID + } + + change := false + if o.Cmd.Flags().Changed("router") { + setJSONValue(entityData, router, "router") + change = true + } + + if o.Cmd.Flags().Changed("binding") { + setJSONValue(entityData, o.binding, "binding") + change = true + } + + if o.Cmd.Flags().Changed("address") { + setJSONValue(entityData, o.address, "address") + change = true + } + + if o.Cmd.Flags().Changed("cost") { + if o.cost > math.MaxUint16 { + return errors2.Errorf("Invalid cost %v. Must be positive number less than or equal to %v", o.cost, math.MaxUint16) + } + setJSONValue(entityData, o.cost, "cost") + change = true + } + + if o.Cmd.Flags().Changed("precedence") { + validValues := []string{"default", "required", "failed"} + if !stringz.Contains(validValues, o.precedence) { + return errors2.Errorf("Invalid precedence %v. Must be one of %+v", o.precedence, validValues) + } + setJSONValue(entityData, o.precedence, "precedence") + change = true + } + + if !change { + return errors.New("no change specified. must specify at least one attribute to change") + } + + _, err = patchEntityOfType(fmt.Sprintf("terminators/%v", o.Args[0]), entityData.String(), &o.commonOptions) + return err +} diff --git a/ziti/cmd/ziti/util/rest.go b/ziti/cmd/ziti/util/rest.go index 90cedb304..3b399f933 100644 --- a/ziti/cmd/ziti/util/rest.go +++ b/ziti/cmd/ziti/util/rest.go @@ -24,10 +24,10 @@ import ( "fmt" "github.com/Jeffail/gabs" "github.com/blang/semver" + "github.com/openziti/foundation/common/constants" "github.com/openziti/ziti/common/version" cmdhelper "github.com/openziti/ziti/ziti/cmd/ziti/cmd/helpers" c "github.com/openziti/ziti/ziti/cmd/ziti/constants" - "github.com/openziti/foundation/common/constants" "gopkg.in/resty.v1" "io" "net/http" @@ -577,7 +577,7 @@ func EdgeControllerDelete(entityType string, id string, out io.Writer, logJSON b } // EdgeControllerUpdate will update entities of the given type in the given Edge Controller -func EdgeControllerUpdate(entityType string, body string, out io.Writer, put bool, logJSON bool) (*gabs.Container, error) { +func EdgeControllerUpdate(entityType string, body string, out io.Writer, method string, logJSON bool) (*gabs.Container, error) { session := &Session{} if err := session.Load(); err != nil { return nil, err @@ -589,26 +589,19 @@ func EdgeControllerUpdate(entityType string, body string, out io.Writer, put boo client.SetRootCertificate(session.Cert) } - request := client. + resp, err := client. R(). SetHeader("Content-Type", "application/json"). SetHeader(constants.ZitiSession, session.Token). - SetBody(body) - - var err error - var resp *resty.Response - if put { - resp, err = request.Put(session.Host + "/" + entityType) - } else { - resp, err = request.Patch(session.Host + "/" + entityType) - } + SetBody(body). + Execute(method, session.Host+"/"+entityType) if err != nil { return nil, fmt.Errorf("unable to update %v instance in Ziti Edge Controller at %v. Error: %v", entityType, session.Host, err) } if resp.StatusCode() != http.StatusOK { - return nil, fmt.Errorf("error creating %v instance in Ziti Edge Controller at %v. Status code: %v, Server returned: %v", + return nil, fmt.Errorf("error updating %v instance in Ziti Edge Controller at %v. Status code: %v, Server returned: %v", entityType, session.Host, resp.Status(), resp.String()) } From 5af1e97037a4ade160adb4b4df90328f5743dc85 Mon Sep 17 00:00:00 2001 From: Paul Lorenz Date: Mon, 1 Jun 2020 14:07:59 -0400 Subject: [PATCH 2/2] DEBUG -> ZITI_CLI_DEBUG. Allow filtering by config type name when listing services --- ziti/cmd/ziti/cmd/edge_controller/common.go | 6 ++++-- ziti/cmd/ziti/cmd/edge_controller/list.go | 15 ++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/ziti/cmd/ziti/cmd/edge_controller/common.go b/ziti/cmd/ziti/cmd/edge_controller/common.go index d2f3cdb28..c819e14ff 100644 --- a/ziti/cmd/ziti/cmd/edge_controller/common.go +++ b/ziti/cmd/ziti/cmd/edge_controller/common.go @@ -49,7 +49,7 @@ func mapNameToID(entityType string, val string) (string, error) { entity := list[0] entityId, _ := entity.Path("id").Data().(string) - if val, found := os.LookupEnv("DEBUG"); found && strings.EqualFold("true", val) { + if val, found := os.LookupEnv("ZITI_CLI_DEBUG"); found && strings.EqualFold("true", val) { fmt.Printf("Found %v with id %v for name %v\n", entityType, entityId, val) } return entityId, nil @@ -89,7 +89,9 @@ func mapNamesToIDs(entityType string, list ...string) ([]string, error) { for _, entity := range list { entityId, _ := entity.Path("id").Data().(string) result = append(result, entityId) - fmt.Printf("Found %v with id %v for name %v\n", entityType, entityId, val) + if val, found := os.LookupEnv("ZITI_CLI_DEBUG"); found && strings.EqualFold("true", val) { + fmt.Printf("Found %v with id %v for name %v\n", entityType, entityId, val) + } } } } diff --git a/ziti/cmd/ziti/cmd/edge_controller/list.go b/ziti/cmd/ziti/cmd/edge_controller/list.go index 6be55feba..ca049326c 100644 --- a/ziti/cmd/ziti/cmd/edge_controller/list.go +++ b/ziti/cmd/ziti/cmd/edge_controller/list.go @@ -181,7 +181,7 @@ func newListCmdForEntityType(entityType string, command listCommandRunner, optio // newListServicesCmd creates the list command for the given entity type func newListServicesCmd(options *commonOptions) *cobra.Command { var asIdentity string - var configTypes string + var configTypes []string var roleFilters []string var roleSemantic string @@ -204,7 +204,7 @@ func newListServicesCmd(options *commonOptions) *cobra.Command { cmd.Flags().BoolVarP(&options.OutputJSONResponse, "output-json", "j", false, "Output the full JSON response from the Ziti Edge Controller") cmd.Flags().StringVar(&asIdentity, "as-identity", "", "Allow admins to see services as they would be seen by a different identity") - cmd.Flags().StringVar(&configTypes, "config-types", "", "Override which config types to view on services") + cmd.Flags().StringSliceVar(&configTypes, "config-types", nil, "Override which config types to view on services") cmd.Flags().StringSliceVar(&roleFilters, "role-filters", nil, "Allow filtering by roles") cmd.Flags().StringVar(&roleSemantic, "role-semantic", "", "Specify which roles semantic to use ") @@ -468,7 +468,7 @@ func outputTerminators(o *commonOptions, children []*gabs.Container, pagingInfo return nil } -func runListServices(asIdentity string, configTypes string, roleFilters []string, roleSemantic string, options *commonOptions) error { +func runListServices(asIdentity string, configTypes []string, roleFilters []string, roleSemantic string, options *commonOptions) error { params := url.Values{} if len(options.Args) > 0 { params.Add("filter", options.Args[0]) @@ -476,8 +476,13 @@ func runListServices(asIdentity string, configTypes string, roleFilters []string if asIdentity != "" { params.Add("asIdentity", asIdentity) } - if configTypes != "" { - params.Add("configTypes", configTypes) + + if configTypes, err := mapNamesToIDs("config-types", configTypes...); err != nil { + return err + } else { + for _, configType := range configTypes { + params.Add("configTypes", configType) + } } for _, roleFilter := range roleFilters { params.Add("roleFilter", roleFilter)