diff --git a/README.md b/README.md index 70507db..e24d6f5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # tools -[![copyright module docs](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/efficientgo/tools/copyright) -[![core module docs](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/efficientgo/tools/core) -[![e2e module docs](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/efficientgo/tools/e2e) +[![copyright module docs](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/efficientgo/tools/copyright) [![core module docs](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/efficientgo/tools/core) [![e2e module docs](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/efficientgo/tools/e2e) Set of lightweight tools, packages and modules that every open-source Go project always needs with almost no dependencies. @@ -207,3 +205,25 @@ or via [bingo](https://github.com/bwplotka/bingo) if want to pin it: go install github.com/bwplotka/bingo bingo get -u github.com/efficientgo/tools/copyright ``` + +### Module `github.com/efficientgo/tools/extkingpin` + +This module provides the PathOrContent flag type which defines two flags to fetch bytes. Either from file (\*-file flag) or content (\* flag). Also returns the content of a YAML file with substituted environment variables. + +```go mdox-gen-exec="sh -c 'tail -n +6 extkingpin/doc.go'" +// PathOrContent is a flag type that defines two flags to fetch bytes. Either from file (*-file flag) or content (* flag). +// Also returns content of YAML file with substituted environment variables. +// Follows K8s convention, i.e $(...), as mentioned here https://kubernetes.io/docs/tasks/inject-data-application/define-interdependent-environment-variables/. + +// RegisterPathOrContent registers PathOrContent flag in kingpinCmdClause. + +// Content returns the content of the file when given or directly the content that has been passed to the flag. +// It returns an error when: +// * The file and content flags are both not empty. +// * The file flag is not empty but the file can't be read. +// * The content is empty and the flag has been defined as required. + +// Option is a functional option type for PathOrContent objects. +// WithRequired allows you to override default required option. +// WithEnvSubstitution allows you to override default envSubstitution option. +``` diff --git a/extkingpin/doc.go b/extkingpin/doc.go new file mode 100644 index 0000000..f07082b --- /dev/null +++ b/extkingpin/doc.go @@ -0,0 +1,20 @@ +// Copyright (c) The EfficientGo Authors. +// Licensed under the Apache License 2.0. + +package extkingpin + +// PathOrContent is a flag type that defines two flags to fetch bytes. Either from file (*-file flag) or content (* flag). +// Also returns content of YAML file with substituted environment variables. +// Follows K8s convention, i.e $(...), as mentioned here https://kubernetes.io/docs/tasks/inject-data-application/define-interdependent-environment-variables/. + +// RegisterPathOrContent registers PathOrContent flag in kingpinCmdClause. + +// Content returns the content of the file when given or directly the content that has been passed to the flag. +// It returns an error when: +// * The file and content flags are both not empty. +// * The file flag is not empty but the file can't be read. +// * The content is empty and the flag has been defined as required. + +// Option is a functional option type for PathOrContent objects. +// WithRequired allows you to override default required option. +// WithEnvSubstitution allows you to override default envSubstitution option. diff --git a/extkingpin/go.mod b/extkingpin/go.mod new file mode 100644 index 0000000..3a935b5 --- /dev/null +++ b/extkingpin/go.mod @@ -0,0 +1,11 @@ +module github.com/efficientgo/tools/extkingpin + +go 1.15 + +require ( + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect + github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect + github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.7.0 // indirect + gopkg.in/alecthomas/kingpin.v2 v2.2.6 +) diff --git a/extkingpin/go.sum b/extkingpin/go.sum new file mode 100644 index 0000000..65a2924 --- /dev/null +++ b/extkingpin/go.sum @@ -0,0 +1,21 @@ +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4= +github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/extkingpin/pathorcontent.go b/extkingpin/pathorcontent.go new file mode 100644 index 0000000..e02cf4b --- /dev/null +++ b/extkingpin/pathorcontent.go @@ -0,0 +1,130 @@ +// Copyright (c) The EfficientGo Authors. +// Licensed under the Apache License 2.0. + +// Taken from Thanos project. +// +// Copyright (c) The Thanos Authors. +// Licensed under the Apache License 2.0. +package extkingpin + +import ( + "fmt" + "io/ioutil" + "os" + "regexp" + + "github.com/pkg/errors" + "gopkg.in/alecthomas/kingpin.v2" +) + +// PathOrContent is a flag type that defines two flags to fetch bytes. Either from file (*-file flag) or content (* flag). +type PathOrContent struct { + flagName string + + envSubstitution bool + required bool + + path *string + content *string +} + +// Option is a functional option type for PathOrContent objects. +type Option func(*PathOrContent) + +type FlagClause interface { + Flag(name, help string) *kingpin.FlagClause +} + +// RegisterPathOrContent registers PathOrContent flag in kingpinCmdClause. +func RegisterPathOrContent(cmd FlagClause, flagName string, help string, opts ...Option) *PathOrContent { + fileFlagName := fmt.Sprintf("%s-file", flagName) + contentFlagName := flagName + + fileHelp := fmt.Sprintf("Path to %s", help) + fileFlag := cmd.Flag(fileFlagName, fileHelp).PlaceHolder("").String() + + contentHelp := fmt.Sprintf("Alternative to '%s' flag (mutually exclusive). Content of %s", fileFlagName, help) + contentFlag := cmd.Flag(contentFlagName, contentHelp).PlaceHolder("").String() + + p := &PathOrContent{ + flagName: flagName, + path: fileFlag, + content: contentFlag, + required: false, + envSubstitution: false, + } + for _, opt := range opts { + opt(p) + } + return p +} + +// Content returns the content of the file when given or directly the content that has been passed to the flag. +// It returns an error when: +// * The file and content flags are both not empty. +// * The file flag is not empty but the file can't be read. +// * The content is empty and the flag has been defined as required. +func (p *PathOrContent) Content() ([]byte, error) { + fileFlagName := fmt.Sprintf("%s-file", p.flagName) + + if len(*p.path) > 0 && len(*p.content) > 0 { + return nil, errors.Errorf("both %s and %s flags set.", fileFlagName, p.flagName) + } + + var content []byte + if len(*p.path) > 0 { + c, err := ioutil.ReadFile(*p.path) + if err != nil { + return nil, errors.Wrapf(err, "loading file %s for %s", *p.path, fileFlagName) + } + content = c + } else { + content = []byte(*p.content) + } + + if len(content) == 0 && p.required { + return nil, errors.Errorf("flag %s or %s is required for running this command and content cannot be empty.", fileFlagName, p.flagName) + } + if p.envSubstitution { + replace, err := expandEnv(content) + if err != nil { + return nil, err + } + content = replace + } + return content, nil +} + +// WithRequired allows you to override default required option. +func WithRequired() Option { + return func(p *PathOrContent) { + p.required = true + } +} + +// WithEnvSubstitution allows you to override default envSubstitution option. +func WithEnvSubstitution() Option { + return func(p *PathOrContent) { + p.envSubstitution = true + } +} + +// expandEnv returns content of YAML file with substituted environment variables. +// Follows K8s convention, i.e $(...), as mentioned here https://kubernetes.io/docs/tasks/inject-data-application/define-interdependent-environment-variables/. +func expandEnv(b []byte) (r []byte, err error) { + var envRe = regexp.MustCompile(`\$\(([a-zA-Z_0-9]+)\)`) + r = envRe.ReplaceAllFunc(b, func(n []byte) []byte { + if err != nil { + return nil + } + n = n[2 : len(n)-1] + + v, ok := os.LookupEnv(string(n)) + if !ok { + err = errors.Errorf("found reference to unset environment variable %q", n) + return nil + } + return []byte(v) + }) + return r, err +}