diff --git a/cmd/file_namespace.go b/cmd/file_namespace.go new file mode 100644 index 000000000..959487fbe --- /dev/null +++ b/cmd/file_namespace.go @@ -0,0 +1,146 @@ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/go-apiops/deckformat" + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/jsonbasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/namespace" + "github.com/kong/go-apiops/yamlbasics" + "github.com/spf13/cobra" +) + +var ( + cmdNamespaceInputFilename string + cmdNamespaceOutputFilename string + cmdNamespaceOutputFormat string + cmdNamespaceSelectors []string + cmdNamespacePathPrefix string + cmdNamespaceAllowEmptySelectors bool +) + +// Executes the CLI command "namespace" +func executeNamespace(cmd *cobra.Command, _ []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + _ = sendAnalytics("file-namespace", "", modeLocal) + + err := namespace.CheckNamespace(cmdNamespacePathPrefix) + if err != nil { + return fmt.Errorf("invalid path-prefix '%s': %w", cmdNamespacePathPrefix, err) + } + + cmdNamespaceOutputFormat = strings.ToUpper(cmdNamespaceOutputFormat) + + trackInfo := deckformat.HistoryNewEntry("namespace") + trackInfo["input"] = cmdNamespaceInputFilename + trackInfo["output"] = cmdNamespaceOutputFilename + trackInfo["selectors"] = cmdNamespaceSelectors + trackInfo["path-prefix"] = cmdNamespacePathPrefix + + // do the work: read/namespace/write + data, err := filebasics.DeserializeFile(cmdNamespaceInputFilename) + if err != nil { + return fmt.Errorf("failed to read input file '%s'; %w", cmdNamespaceInputFilename, err) + } + deckformat.HistoryAppend(data, trackInfo) + + yamlNode := jsonbasics.ConvertToYamlNode(data) + + // var selectors yamlbasics.SelectorSet + selectors, err := yamlbasics.NewSelectorSet(cmdNamespaceSelectors) + if err != nil { + return err + } + + err = namespace.Apply(yamlNode, selectors, cmdNamespacePathPrefix, cmdNamespaceAllowEmptySelectors) + if err != nil { + return fmt.Errorf("failed to apply the namespace: %w", err) + } + + data = jsonbasics.ConvertToJSONobject(yamlNode) + + return filebasics.WriteSerializedFile(cmdNamespaceOutputFilename, data, + filebasics.OutputFormat(cmdNamespaceOutputFormat)) +} + +// +// +// Define the CLI data for the namespace command +// +// + +func newNamespaceCmd() *cobra.Command { + namespaceCmd := &cobra.Command{ + Use: "namespace [flags]", + Short: "Apply a namespace to routes in a decK file by prefixing the path", + Long: `Apply a namespace to routes in a decK file by prefixing the path. + +By prefixing paths with a specific segment, colliding paths to services can be +namespaced to prevent the collisions. Eg. 2 API definitions that both expose a +'/list' path. By prefixing one with '/addressbook' and the other with '/cookbook' +the resulting paths '/addressbook/list' and '/cookbook/list' can be exposed without +colliding. + +To remove the prefix from the path before the request is routed to the service, the +following approaches are used: +- if the route has 'strip_path=true' then the added prefix will already be stripped +- if the related service has a 'path' property that matches the prefix, then the + 'service.path' property is updated to remove the prefix +- a "pre-function" plugin will be added to remove the prefix from the path + +`, + RunE: executeNamespace, + Example: `# Apply namespace to a deckfile +deck file namespace --path-prefix=/kong --state=deckfile.yaml + +# Apply namespace to a deckfile, and write to a new file +# Example file 'kong.yaml': +routes: +- paths: + - ~/tracks/system$ + strip_path: true +- paths: + - ~/list$ + strip_path: false + +# Apply namespace to the deckfile, and write to stdout: +cat kong.yaml | deck file namespace --path-prefix=/kong + +# Output: +routes: +- paths: + - ~/kong/tracks/system$ + strip_path: true +- paths: + - ~/kong/list$ + strip_path: false + plugins: + - name: pre-function + config: + access: + - "local ns='/kong' -- this strips the '/kong' namespace from the path\nlocal " + +`, + } + + namespaceCmd.Flags().StringVarP(&cmdNamespaceInputFilename, "state", "s", "-", + "decK file to process. Use - to read from stdin.") + namespaceCmd.Flags().StringVarP(&cmdNamespaceOutputFilename, "output-file", "o", "-", + "Output file to write. Use - to write to stdout.") + namespaceCmd.Flags().StringVarP(&cmdNamespaceOutputFormat, "format", "", "yaml", + "Output format: yaml or json.") + namespaceCmd.Flags().StringArrayVarP(&cmdNamespaceSelectors, "selector", "", []string{}, + "json-pointer identifying element to patch. Repeat for multiple selectors. Defaults "+ + "to selecting all routes.") + namespaceCmd.Flags().StringVarP(&cmdNamespacePathPrefix, "path-prefix", "p", "", + "The path based namespace to apply.") + namespaceCmd.Flags().BoolVarP(&cmdNamespaceAllowEmptySelectors, "allow-empty-selectors", + "", false, "do not error out if the selectors return empty") + + return namespaceCmd +} diff --git a/cmd/root.go b/cmd/root.go index 0b0f534e3..c5844bd51 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -249,6 +249,7 @@ It can be used to export, import, or sync entities to Kong.`, fileCmd.AddCommand(newOpenapi2KongCmd()) fileCmd.AddCommand(newFileRenderCmd()) fileCmd.AddCommand(newLintCmd()) + fileCmd.AddCommand(newNamespaceCmd()) fileCmd.AddCommand(newConvertCmd(false)) fileCmd.AddCommand(newValidateCmd(false, false)) // file-based validation } diff --git a/go.mod b/go.mod index bcdb1dda7..f18934418 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/daveshanley/vacuum v0.5.0 github.com/fatih/color v1.15.0 github.com/google/go-cmp v0.6.0 - github.com/kong/go-apiops v0.1.27 + github.com/kong/go-apiops v0.1.29 github.com/kong/go-database-reconciler v1.3.1 github.com/kong/go-kong v0.50.0 github.com/mitchellh/go-homedir v1.1.0 diff --git a/go.sum b/go.sum index 33ed8f63d..d8de26bac 100644 --- a/go.sum +++ b/go.sum @@ -180,8 +180,8 @@ github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuOb github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/kong/go-apiops v0.1.27 h1:Jy9HSBtGtO0r0UpXSFenCfOUA0QmeX4i8iGQU0vnC+Q= -github.com/kong/go-apiops v0.1.27/go.mod h1:TYRNVbQ/lw6D3AUJBVP1w4zmMlJ59K83q+zCFa02uZ4= +github.com/kong/go-apiops v0.1.29 h1:c+AB8MmGIr+K01Afm4GB2xaOmJnD/8KWMJQkr9qssnc= +github.com/kong/go-apiops v0.1.29/go.mod h1:ZNdiTZyVrAssB4wjEYWV7BfpcV9UME9LxnDDZhMPuNU= github.com/kong/go-database-reconciler v1.3.1 h1:lppM6Tc7aGIpGMWax1zDgE0H4/ghSbB4c3Vt/E0OnzM= github.com/kong/go-database-reconciler v1.3.1/go.mod h1:kcK/+GpWqC4v0QJIKCfb1iZ3u+bKvQBfI1AxtayXdYs= github.com/kong/go-kong v0.50.0 h1:HDKn3o/02AH4cURvjzS09gqC4bHZDva5H8JJwYi7T1U= @@ -240,15 +240,15 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= -github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= +github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= -github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= +github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= github.com/pb33f/libopenapi v0.13.11 h1:CHRT15/iakHcwRvr9y7bf63UPekXa8FB1Sc4D4BZ7NU= github.com/pb33f/libopenapi v0.13.11/go.mod h1:Lv2eEtsAtbRFlF8hjH82L8SIGoUNgemMVoKoB6A9THk= github.com/pb33f/libopenapi-validator v0.0.28 h1:XOKGLuRLkHtkiPvm4x1JZgqVqFyD2tPx15qx+aSeaBE= @@ -350,6 +350,8 @@ github.com/yudai/pp v2.0.2-0.20150410014804-be8315415630+incompatible/go.mod h1: github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=