Add support for ziti-for-each

This commit is contained in:
Paul Lorenz
2022-04-08 08:56:12 -04:00
parent 4acbd68c1c
commit 133677cd91
6 changed files with 205 additions and 30 deletions
+13 -1
View File
@@ -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
@@ -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
@@ -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)
}
+12 -9
View File
@@ -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
}
@@ -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
}
+73 -9
View File
@@ -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 {