Skip to content

Commit

Permalink
Support reading from stdin
Browse files Browse the repository at this point in the history
  • Loading branch information
Warren Fernandes committed Jul 29, 2020
1 parent 12a1960 commit dc76005
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 13 deletions.
30 changes: 27 additions & 3 deletions cmd/clusterctl/client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package client

import (
"io"
"io/ioutil"
"strconv"

"k8s.io/utils/pointer"
Expand All @@ -26,6 +28,7 @@ import (
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor"
)

func (c *clusterctlClient) GetProvidersConfig() ([]Provider, error) {
Expand Down Expand Up @@ -58,8 +61,15 @@ func (c *clusterctlClient) GetProviderComponents(provider string, providerType c
return components, nil
}

// ReaderSourceOptions define the options to be used when reading a template
// from an arbitrary reader
type ReaderSourceOptions struct {
Reader io.Reader
}

// ProcessYAMLOptions are the options supported by ProcessYAML.
type ProcessYAMLOptions struct {
ReaderSource *ReaderSourceOptions
// URLSource to be used for reading the template
URLSource *URLSourceOptions

Expand All @@ -69,6 +79,20 @@ type ProcessYAMLOptions struct {
}

func (c *clusterctlClient) ProcessYAML(options ProcessYAMLOptions) (YamlPrinter, error) {
if options.ReaderSource != nil {
content, err := ioutil.ReadAll(options.ReaderSource.Reader)
if err != nil {
return nil, err
}
return repository.NewTemplate(repository.TemplateInput{
RawArtifact: content,
ConfigVariablesClient: c.configClient.Variables(),
Processor: yaml.NewSimpleProcessor(),
TargetNamespace: "",
ListVariablesOnly: options.ListVariablesOnly,
})
}

// Technically we do not need to connect to the cluster. However, we are
// leveraging the template client which exposes GetFromURL() is available
// on the cluster client so we create a cluster client with default
Expand All @@ -83,11 +107,11 @@ func (c *clusterctlClient) ProcessYAML(options ProcessYAMLOptions) (YamlPrinter,
return nil, err
}

if options.URLSource == nil {
return nil, errors.New("unable to read custom template. Please specify a template source")
if options.URLSource != nil {
return c.getTemplateFromURL(cluster, *options.URLSource, "", options.ListVariablesOnly)
}

return c.getTemplateFromURL(cluster, *options.URLSource, "", options.ListVariablesOnly)
return nil, errors.New("unable to read custom template. Please specify a template source")
}

// GetClusterTemplateOptions carries the options supported by GetClusterTemplate.
Expand Down
35 changes: 35 additions & 0 deletions cmd/clusterctl/client/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"

. "github.com/onsi/gomega"
"github.com/pkg/errors"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -611,6 +613,8 @@ v3: ${VAR3:-default3}`
templateFile := filepath.Join(dir, "template.yaml")
g.Expect(ioutil.WriteFile(templateFile, []byte(template), 0600)).To(Succeed())

inputReader := strings.NewReader(template)

tests := []struct {
name string
options ProcessYAMLOptions
Expand Down Expand Up @@ -649,6 +653,30 @@ v3: default3`,
options: ProcessYAMLOptions{},
expectErr: true,
},
{
name: "processes yaml from specified reader",
options: ProcessYAMLOptions{
ReaderSource: &ReaderSourceOptions{
Reader: inputReader,
},
ListVariablesOnly: false,
},
expectErr: false,
expectedYaml: `v1: default1
v2: default2
v3: default3`,
expectedVars: []string{"VAR1", "VAR2", "VAR3"},
},
{
name: "returns error if unable to read from reader",
options: ProcessYAMLOptions{
ReaderSource: &ReaderSourceOptions{
Reader: &errReader{},
},
ListVariablesOnly: false,
},
expectErr: true,
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -676,3 +704,10 @@ v3: default3`,
}

}

// errReader returns a non-EOF error on the first read.
type errReader struct{}

func (e *errReader) Read(p []byte) (n int, err error) {
return 0, errors.New("read error")
}
27 changes: 20 additions & 7 deletions cmd/clusterctl/cmd/generate_yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,24 @@ var generateYamlCmd = &cobra.Command{
# Generates a configuration file with variable values using
a template stored locally.
clusterctl generate yaml --from ~/workspace/cluster-template.yaml`),
clusterctl generate yaml --from ~/workspace/cluster-template.yaml
# Prints list of variables used in the local template
clusterctl generate yaml --from ~/workspace/cluster-template.yaml --list-variables
# Prints list of variables from template passed in via stdin
cat ~/workspace/cluster-template.yaml | clusterctl generate yaml --from - --list-variables
`),

RunE: func(cmd *cobra.Command, args []string) error {
return generateYAML(os.Stdout)
return generateYAML(os.Stdin, os.Stdout)
},
}

func init() {
// flags for the url source
generateYamlCmd.Flags().StringVar(&gyOpts.url, "from", "",
"The URL to read the template from.")
generateYamlCmd.Flags().StringVar(&gyOpts.url, "from", "-",
"The URL to read the template from. It defaults to '-' which reads from stdin.")

// other flags
generateYamlCmd.Flags().BoolVar(&gyOpts.listVariables, "list-variables", false,
Expand All @@ -70,7 +77,7 @@ func init() {
generateCmd.AddCommand(generateYamlCmd)
}

func generateYAML(w io.Writer) error {
func generateYAML(r io.Reader, w io.Writer) error {
c, err := client.New(cfgFile)
if err != nil {
return err
Expand All @@ -79,8 +86,14 @@ func generateYAML(w io.Writer) error {
ListVariablesOnly: gyOpts.listVariables,
}
if gyOpts.url != "" {
options.URLSource = &client.URLSourceOptions{
URL: gyOpts.url,
if gyOpts.url == "-" {
options.ReaderSource = &client.ReaderSourceOptions{
Reader: r,
}
} else {
options.URLSource = &client.URLSourceOptions{
URL: gyOpts.url,
}
}
}
printer, err := c.ProcessYAML(options)
Expand Down
22 changes: 19 additions & 3 deletions cmd/clusterctl/cmd/generate_yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ package cmd

import (
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"

. "github.com/onsi/gomega"
Expand All @@ -29,18 +31,22 @@ import (
func Test_generateYAML(t *testing.T) {
g := NewWithT(t)
// create a local template
template, cleanup1 := createTempFile(g, `v1: ${VAR1:=default1}
contents := `v1: ${VAR1:=default1}
v2: ${VAR2=default2}
v3: ${VAR3:-default3}`)
v3: ${VAR3:-default3}`
template, cleanup1 := createTempFile(g, contents)
defer cleanup1()

templateWithoutVars, cleanup2 := createTempFile(g, `v1: foobar
v2: bazfoo`)
defer cleanup2()

inputReader := strings.NewReader(contents)

tests := []struct {
name string
options *generateYAMLOptions
inputReader io.Reader
expectErr bool
expectedOutput string
}{
Expand Down Expand Up @@ -79,14 +85,24 @@ v3: default3
expectErr: false,
expectedOutput: "\n",
},
{
name: "prints processed yaml using specified reader when '--from=-'",
options: &generateYAMLOptions{url: "-", listVariables: false},
inputReader: inputReader,
expectErr: false,
expectedOutput: `v1: default1
v2: default2
v3: default3
`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
gyOpts = tt.options
buf := bytes.NewBufferString("")
err := generateYAML(buf)
err := generateYAML(inputReader, buf)
if tt.expectErr {
g.Expect(err).To(HaveOccurred())
return
Expand Down
10 changes: 10 additions & 0 deletions docs/book/src/clusterctl/commands/generate-yaml.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ clusterctl generate yaml --from https://github.com/foo-org/foo-repository/blob/m
# Generates a configuration file with variable values using
# a template stored locally.
clusterctl generate yaml --from ~/workspace/cluster-template.yaml

# Prints list of variables used in the local template
clusterctl generate yaml --from ~/workspace/cluster-template.yaml --list-variables

# Prints list of variables from template passed in via stdin
cat ~/workspace/cluster-template.yaml | clusterctl generate yaml --from - --list-variables

# Default behavior for this sub-command is to read from stdin.
# Generate configuration from stdin
cat ~/workspace/cluster-template.yaml | clusterctl generate yaml
```


Expand Down

0 comments on commit dc76005

Please sign in to comment.