From 133677cd9155f8e3d0aec494e44ede8bbbf6d882 Mon Sep 17 00:00:00 2001 From: Paul Lorenz Date: Fri, 8 Apr 2022 08:56:12 -0400 Subject: [PATCH] Add support for ziti-for-each --- ziti/cmd/ziti/cmd/demo/echo-notes.txt | 14 ++- .../simple-router-tunneler-hosted.md | 17 ++- ...p-simple-router-tunneler-hosted-service.go | 1 + ziti/cmd/ziti/cmd/edge/list.go | 21 ++-- .../cmd/tutorial/actions/ziti_for_each.go | 100 ++++++++++++++++++ ziti/cmd/ziti/tutorial/runner.go | 82 ++++++++++++-- 6 files changed, 205 insertions(+), 30 deletions(-) create mode 100644 ziti/cmd/ziti/cmd/tutorial/actions/ziti_for_each.go diff --git a/ziti/cmd/ziti/cmd/demo/echo-notes.txt b/ziti/cmd/ziti/cmd/demo/echo-notes.txt index d99f682d9..34d3aee9c 100644 --- a/ziti/cmd/ziti/cmd/demo/echo-notes.txt +++ b/ziti/cmd/ziti/cmd/demo/echo-notes.txt @@ -6,7 +6,7 @@ Before start Part 1 - single router, single server 1. show zcat and echo-server -2. create first edge router +2. create first edge router, mark with demo attribute 3. client setup 4. do sdk demo 5. look at terminators @@ -14,4 +14,16 @@ Part 1 - single router, single server 7. look at terminators Part 2 - multi-router, single server +1. Add additional edge router, mark with demo attribute +2. start edge router 2 +3. Do sdk demo +4. show multiple terminators +5. Make sure it's tunneler ready + a. make sure it's a tunneler + b. Add identity attributes: ziti edge update identity edge-router-2 -a echo,echo-host +6. Do tunneler demo +7. Show multiple terminators + +Part 3 - multi-router, multi-server, HS + diff --git a/ziti/cmd/ziti/cmd/demo/setup-scripts/simple-router-tunneler-hosted.md b/ziti/cmd/ziti/cmd/demo/setup-scripts/simple-router-tunneler-hosted.md index 3971bf9bb..f76a78605 100644 --- a/ziti/cmd/ziti/cmd/demo/setup-scripts/simple-router-tunneler-hosted.md +++ b/ziti/cmd/ziti/cmd/demo/setup-scripts/simple-router-tunneler-hosted.md @@ -22,7 +22,7 @@ ziti edge login ```action:ziti ziti edge delete service echo ziti edge delete config echo-host -ziti edge delete identities zcat echo-host +ziti edge delete identities echo-host ziti edge delete service-policies echo-bind ziti edge delete edge-router-policies echo ziti edge delete service-edge-router-policies echo @@ -48,18 +48,13 @@ ziti edge delete service-edge-router-policies echo ziti edge create service echo -c echo-host -a echo ``` -## Select an edge router to host this service on +## Update edge-routers -```action:select-edge-router -Pick the name of an edge router to use. It will be referenced in this tutorial as ${edgeRouterName} -``` +Make sure demo edge routers are tunneler enabled and the associated identity has the `echo-host` attribute -## Update edge router attributes - -Update the select edge router with the `echo-host` attribute, so it gets tied into the policies. - -```action:ziti templatize=true -ziti edge update identity ${edgeRouterName} -a echo-host +```action:ziti-for-each type=edge-routers minCount=1 maxCount=2 filter='anyOf(roleAttributes)="demo"' +ziti edge update edge-router ${entityName} --tunneler-enabled +ziti edge update identity ${entityName} --role-attributes echo-host ``` ## Configure policies diff --git a/ziti/cmd/ziti/cmd/demo/setup-simple-router-tunneler-hosted-service.go b/ziti/cmd/ziti/cmd/demo/setup-simple-router-tunneler-hosted-service.go index 79bdc5bae..81b408595 100644 --- a/ziti/cmd/ziti/cmd/demo/setup-simple-router-tunneler-hosted-service.go +++ b/ziti/cmd/ziti/cmd/demo/setup-simple-router-tunneler-hosted-service.go @@ -81,6 +81,7 @@ func (self *simpleRouterTunnelerHosted) run() error { t.RegisterActionHandler("keep-session-alive", &actions.KeepSessionAliveAction{}) t.RegisterActionHandler("ziti-create-config", &actions.ZitiCreateConfigAction{}) t.RegisterActionHandler("select-edge-router", &actions.SelectEdgeRouterAction{}) + t.RegisterActionHandler("ziti-for-each", &actions.ZitiForEach{}) return t.Run(simpleRouterTunnelerHostedScriptSource) } diff --git a/ziti/cmd/ziti/cmd/edge/list.go b/ziti/cmd/ziti/cmd/edge/list.go index b3b89bd3d..e66ed93fe 100644 --- a/ziti/cmd/ziti/cmd/edge/list.go +++ b/ziti/cmd/ziti/cmd/edge/list.go @@ -371,7 +371,7 @@ func runListEdgeRouters(roleFilters []string, roleSemantic string, options *api. if roleSemantic != "" { params.Add("roleSemantic", roleSemantic) } - children, paging, err := api.ListEntitiesOfType("edge", "edge-routers", params, options.OutputJSONResponse, options.Out, options.Timeout, options.Verbose) + children, paging, err := api.ListEntitiesOfType(util.EdgeAPI, "edge-routers", params, options.OutputJSONResponse, options.Out, options.Timeout, options.Verbose) if err != nil { return err } @@ -679,16 +679,19 @@ func outputIdentities(o *api.Options, children []*gabs.Container, pagingInfo *ap return nil } + t := table.NewWriter() + t.SetStyle(table.StyleRounded) + t.AppendHeader(table.Row{"ID", "Name", "Type", "Attributes"}) + for _, entity := range children { - id, _ := entity.Path("id").Data().(string) - name, _ := entity.Path("name").Data().(string) - typeName, _ := entity.Path("type.name").Data().(string) - roleAttributes := entity.Path("roleAttributes").String() - if _, err := fmt.Fprintf(o.Out, "id: %v name: %v type: %v role attributes: %v\n", id, name, typeName, roleAttributes); err != nil { - return err - } + wrapper := api.Wrap(entity) + t.AppendRow(table.Row{ + wrapper.String("id"), + wrapper.String("name"), + wrapper.String("type.name"), + strings.Join(wrapper.StringSlice("roleAttributes"), "\n")}) } - pagingInfo.Output(o) + api.RenderTable(o, t, pagingInfo) return nil } diff --git a/ziti/cmd/ziti/cmd/tutorial/actions/ziti_for_each.go b/ziti/cmd/ziti/cmd/tutorial/actions/ziti_for_each.go new file mode 100644 index 000000000..82c75dfd8 --- /dev/null +++ b/ziti/cmd/ziti/cmd/tutorial/actions/ziti_for_each.go @@ -0,0 +1,100 @@ +/* + 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 actions + +import ( + "github.com/openziti/ziti/ziti/cmd/ziti/cmd/api" + "github.com/openziti/ziti/ziti/cmd/ziti/tutorial" + "github.com/openziti/ziti/ziti/cmd/ziti/util" + "github.com/pkg/errors" + "net/url" + "os" + "strconv" +) + +type ZitiForEach struct{} + +func (self *ZitiForEach) Execute(ctx *tutorial.ActionContext) error { + ctx.Headers["templatize"] = "true" + apiType, found := ctx.Headers["api"] + if !found { + apiType = string(util.EdgeAPI) + } + entityType := ctx.Headers["type"] + + params := url.Values{} + filter, found := ctx.Headers["filter"] + if found { + params.Add("filter", filter) + } + + minCount := 1 + if count, found := ctx.Headers["minCount"]; found { + val, err := strconv.Atoi(count) + if err != nil { + return errors.Wrapf(err, "couldn't parse minCount, invalid value '%v'", count) + } + if val < 0 { + return errors.Wrapf(err, "invalid minCount, invalid value '%v', must >= 0", count) + } + minCount = val + } + + maxCount := 1 + if count, found := ctx.Headers["maxCount"]; found { + val, err := strconv.Atoi(count) + if err != nil { + return errors.Wrapf(err, "couldn't parse maxCount, invalid value '%v'", count) + } + if val < minCount { + return errors.Wrapf(err, "invalid maxCount '%v', must >= minCount of %v", count, minCount) + } + maxCount = val + } + + entities, _, err := api.ListEntitiesOfType(util.API(apiType), entityType, params, false, os.Stdout, 1, false) + if err != nil { + return err + } + + if len(entities) < minCount { + return errors.Errorf("expected at least %v %v, only found %v", minCount, entityType, len(entities)) + } + + if len(entities) > maxCount { + return errors.Errorf("expected at most %v %v, only found %v", maxCount, entityType, len(entities)) + } + + runner := ZitiRunnerAction{} + originalBody := ctx.Body + for _, entity := range entities { + wrapper := api.Wrap(entity) + id := wrapper.String("id") + name := wrapper.String("name") + + ctx.Runner.AddVariable("entityId", id) + ctx.Runner.AddVariable("entityName", name) + if err := runner.Execute(ctx); err != nil { + return err + } + ctx.Runner.ClearVariable("entityId") + ctx.Runner.ClearVariable("entityName") + ctx.Body = originalBody + } + + return nil +} diff --git a/ziti/cmd/ziti/tutorial/runner.go b/ziti/cmd/ziti/tutorial/runner.go index 43360ee85..6290e4f41 100644 --- a/ziti/cmd/ziti/tutorial/runner.go +++ b/ziti/cmd/ziti/tutorial/runner.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/valyala/fasttemplate" "io" + "os" "sort" "strings" "time" @@ -22,6 +23,14 @@ const ( actionMarker = "f572d396fae9206628714fb2ce00f72e94f2258f" ) +var debug bool + +func init() { + if val, found := os.LookupEnv("DEBUG"); found && strings.EqualFold(val, "true") { + debug = true + } +} + func NewRunner() *Runner { result := &Runner{ actionHandlers: map[string]ActionHandler{}, @@ -70,6 +79,10 @@ func (self *Runner) AddVariable(name string, val interface{}) { self.variables[name] = val } +func (self *Runner) ClearVariable(name string) { + delete(self.variables, name) +} + func (self *Runner) Run(source []byte) error { markdownSource, err := self.transformSource(source) if err != nil { @@ -147,7 +160,10 @@ func (self *Runner) transformSource(source []byte) ([]byte, error) { body = strings.TrimSpace(string(actionSource[nlIndex+1 : endIndex])) } actionDef := strings.TrimSpace(actionHeader) - action, headers := self.parseAction(actionDef) + action, headers, err := self.parseAction(actionDef) + if err != nil { + return nil, err + } handler, ok := self.actionHandlers[action] if !ok { @@ -171,26 +187,74 @@ func (self *Runner) transformSource(source []byte) ([]byte, error) { } } -func (self *Runner) parseAction(s string) (string, map[string]string) { +func (self *Runner) parseAction(s string) (string, map[string]string, error) { var action string headers := map[string]string{} idx := strings.IndexByte(s, ' ') if idx < 1 { - return s, headers + return s, headers, nil } action = s[:idx] rest := s[idx+1:] - for _, kv := range strings.Split(rest, " ") { - vals := strings.Split(kv, "=") - if len(vals) < 2 { - headers[vals[0]] = "" + for len(rest) > 0 { + idx = strings.IndexByte(rest, '=') + if idx < 0 { + headers[rest] = "" + if debug { + fmt.Printf("%v: added %v=%v\n", action, rest, headers[rest]) + } + rest = "" + } else if idx == 0 { + return "", nil, errors.Errorf("unable to parse action %v, unexpected =", action) } else { - headers[vals[0]] = vals[1] + key := rest[:idx] + rest = rest[idx+1:] + if rest[0] == '\'' { + rest = rest[1:] + closingQuoteIdx := strings.IndexByte(rest, '\'') + if closingQuoteIdx < 0 { + return "", nil, errors.Errorf("unable to parse action %v, unclosed single-quote", action) + } + headers[key] = rest[:closingQuoteIdx] + if debug { + fmt.Printf("%v: added %v=%v\n", action, key, headers[key]) + } + rest = rest[closingQuoteIdx+1:] + if len(rest) > 0 { + if rest[0] != ' ' { + return "", nil, errors.Errorf("unable to parse action %v, expected space after closing singe-quote", action) + } else { + rest = rest[1:] + } + } + } else { + idx = strings.IndexByte(rest, ' ') + if idx < 0 { + headers[key] = rest + if debug { + fmt.Printf("%v: added %v=%v\n", action, key, headers[key]) + } + rest = "" + } else if idx == 0 { + headers[key] = "" + if debug { + fmt.Printf("%v: added %v=%v\n", action, key, headers[key]) + } + rest = rest[1:] + } else { + headers[key] = rest[:idx] + if debug { + fmt.Printf("%v: added %v=%v\n", action, key, headers[key]) + } + rest = rest[idx+1:] + } + } } } - return action, headers + + return action, headers, nil } func (self *Runner) actionIds() []string {