feat: port stacks command from influxdb (#168)

This commit is contained in:
Daniel Moran 2021-06-30 17:21:50 -04:00 committed by GitHub
parent 8126bd8397
commit 40fc1845e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 770 additions and 74 deletions

View File

@ -6,6 +6,7 @@ import (
"github.com/influxdata/influx-cli/v2/api"
"github.com/influxdata/influx-cli/v2/clients"
"github.com/influxdata/influx-cli/v2/pkg/template"
)
type Client struct {
@ -15,7 +16,7 @@ type Client struct {
}
type Params struct {
OutParams
template.OutParams
StackId string
IdsPerType map[string][]string
@ -52,14 +53,14 @@ func (c Client) Export(ctx context.Context, params *Params) error {
if err != nil {
return fmt.Errorf("failed to export template: %w", err)
}
if err := params.OutParams.writeTemplate(tmpl); err != nil {
if err := params.OutParams.WriteTemplate(tmpl); err != nil {
return fmt.Errorf("failed to write exported template: %w", err)
}
return nil
}
type AllParams struct {
OutParams
template.OutParams
OrgId string
OrgName string
@ -105,14 +106,14 @@ func (c Client) ExportAll(ctx context.Context, params *AllParams) error {
if err != nil {
return fmt.Errorf("failed to export template: %w", err)
}
if err := params.OutParams.writeTemplate(tmpl); err != nil {
if err := params.OutParams.WriteTemplate(tmpl); err != nil {
return fmt.Errorf("failed to write exported template: %w", err)
}
return nil
}
type StackParams struct {
OutParams
template.OutParams
StackId string
}
@ -127,7 +128,7 @@ func (c Client) ExportStack(ctx context.Context, params *StackParams) error {
if err != nil {
return fmt.Errorf("failed to export stack %q: %w", params.StackId, err)
}
if err := params.OutParams.writeTemplate(tmpl); err != nil {
if err := params.OutParams.WriteTemplate(tmpl); err != nil {
return fmt.Errorf("failed to write exported template: %w", err)
}

View File

@ -1,41 +0,0 @@
package export
import (
"encoding/json"
"fmt"
"io"
"github.com/influxdata/influx-cli/v2/api"
"gopkg.in/yaml.v3"
)
type OutEncoding int
const (
YamlEncoding OutEncoding = iota
JsonEncoding
)
type OutParams struct {
Out io.Writer
Encoding OutEncoding
}
func (o OutParams) writeTemplate(template []api.TemplateEntry) error {
switch o.Encoding {
case JsonEncoding:
enc := json.NewEncoder(o.Out)
enc.SetIndent("", "\t")
return enc.Encode(template)
case YamlEncoding:
enc := yaml.NewEncoder(o.Out)
for _, entry := range template {
if err := enc.Encode(entry); err != nil {
return err
}
}
default:
return fmt.Errorf("encoding %q is not recognized", o.Encoding)
}
return nil
}

265
clients/stacks/stacks.go Normal file
View File

@ -0,0 +1,265 @@
package stacks
import (
"context"
"fmt"
"sort"
"github.com/influxdata/influx-cli/v2/api"
"github.com/influxdata/influx-cli/v2/clients"
"github.com/influxdata/influx-cli/v2/pkg/template"
)
type Client struct {
clients.CLI
api.StacksApi
api.OrganizationsApi
api.TemplatesApi
}
type ListParams struct {
OrgId string
OrgName string
StackIds []string
StackNames []string
}
func (c Client) List(ctx context.Context, params *ListParams) error {
if params.OrgId == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
return clients.ErrMustSpecifyOrg
}
orgId := params.OrgId
if orgId == "" {
orgName := params.OrgName
if orgName == "" {
orgName = c.ActiveConfig.Org
}
res, err := c.GetOrgs(ctx).Org(orgName).Execute()
if err != nil {
return fmt.Errorf("failed to lookup org with name %q: %w", orgName, err)
}
if len(res.GetOrgs()) == 0 {
return fmt.Errorf("no organization with name %q: %w", orgName, err)
}
orgId = res.GetOrgs()[0].GetId()
}
res, err := c.ListStacks(ctx).OrgID(orgId).Name(params.StackNames).StackID(params.StackIds).Execute()
if err != nil {
return fmt.Errorf("failed to list stacks: %w", err)
}
return c.printStacks(stackPrintOptions{stacks: &res})
}
type InitParams struct {
OrgId string
OrgName string
Name string
Description string
URLs []string
}
func (c Client) Init(ctx context.Context, params *InitParams) error {
if params.OrgId == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
return clients.ErrMustSpecifyOrg
}
orgId := params.OrgId
if orgId == "" {
orgName := params.OrgName
if orgName == "" {
orgName = c.ActiveConfig.Org
}
res, err := c.GetOrgs(ctx).Org(orgName).Execute()
if err != nil {
return fmt.Errorf("failed to lookup org with name %q: %w", orgName, err)
}
if len(res.GetOrgs()) == 0 {
return fmt.Errorf("no organization with name %q: %w", orgName, err)
}
orgId = res.GetOrgs()[0].GetId()
}
req := api.StackPostRequest{
OrgID: orgId,
Name: params.Name,
Urls: params.URLs,
}
if params.Description != "" {
req.Description = &params.Description
}
stack, err := c.CreateStack(ctx).StackPostRequest(req).Execute()
if err != nil {
return fmt.Errorf("failed to create stack %q: %w", params.Name, err)
}
return c.printStacks(stackPrintOptions{stack: &stack})
}
type RemoveParams struct {
OrgId string
OrgName string
Ids []string
Force bool
}
func (c Client) Remove(ctx context.Context, params *RemoveParams) error {
if params.OrgId == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
return clients.ErrMustSpecifyOrg
}
orgId := params.OrgId
if orgId == "" {
orgName := params.OrgName
if orgName == "" {
orgName = c.ActiveConfig.Org
}
res, err := c.GetOrgs(ctx).Org(orgName).Execute()
if err != nil {
return fmt.Errorf("failed to lookup org with name %q: %w", orgName, err)
}
if len(res.GetOrgs()) == 0 {
return fmt.Errorf("no organization with name %q: %w", orgName, err)
}
orgId = res.GetOrgs()[0].GetId()
}
stacks, err := c.ListStacks(ctx).OrgID(orgId).StackID(params.Ids).Execute()
if err != nil {
return fmt.Errorf("failed to look up stacks: %w", err)
}
for _, stack := range stacks.Stacks {
if err := c.printStacks(stackPrintOptions{stack: &stack}); err != nil {
return err
}
if !params.Force && !c.StdIO.GetConfirm(fmt.Sprintf("Confirm removal of the stack[%s] and all associated resources", stack.Id)) {
continue
}
if err := c.DeleteStack(ctx, stack.Id).OrgID(orgId).Execute(); err != nil {
return fmt.Errorf("failed to delete stack %q: %w", stack.Id, err)
}
}
return nil
}
type AddedResource struct {
Kind string
Id string
}
type UpdateParams struct {
Id string
Name *string
Description *string
URLs []string
AddedResources []AddedResource
template.OutParams
}
func (c Client) Update(ctx context.Context, params *UpdateParams) error {
req := api.StackPatchRequest{
Name: params.Name,
Description: params.Description,
TemplateURLs: params.URLs,
AdditionalResources: make([]api.StackPatchRequestResource, len(params.AddedResources)),
}
for i, r := range params.AddedResources {
req.AdditionalResources[i] = api.StackPatchRequestResource{
ResourceID: r.Id,
Kind: r.Kind,
}
}
stack, err := c.UpdateStack(ctx, params.Id).StackPatchRequest(req).Execute()
if err != nil {
return fmt.Errorf("failed to udpate stack %q: %w", params.Id, err)
}
if err := c.printStacks(stackPrintOptions{stack: &stack}); err != nil {
return err
}
// Can skip exporting the updated template if no resources were added.
if len(params.AddedResources) == 0 {
return nil
}
if !c.StdIO.GetConfirm(`Your stack now differs from your template. Applying an outdated template will revert these updates.
Export a new template with these updates to prevent accidental changes?`) {
return nil
}
exportReq := api.TemplateExport{StackID: &stack.Id}
tmpl, err := c.ExportTemplate(ctx).TemplateExport(exportReq).Execute()
if err != nil {
return fmt.Errorf("failed to export stack %q: %w", stack.Id, err)
}
if err := params.OutParams.WriteTemplate(tmpl); err != nil {
return fmt.Errorf("failed to write exported template: %w", err)
}
return nil
}
type stackPrintOptions struct {
stack *api.Stack
stacks *api.Stacks
}
func (c Client) printStacks(options stackPrintOptions) error {
if c.PrintAsJSON {
var v interface{}
if options.stack != nil {
v = options.stack
} else {
v = options.stacks
}
return c.PrintJSON(v)
}
headers := []string{"ID", "OrgID", "Active", "Name", "Description", "Num Resources", "Sources", "URLs", "Created At", "Updated At"}
var stacks []api.Stack
if options.stacks != nil {
stacks = options.stacks.Stacks
}
if options.stack != nil {
stacks = append(stacks, *options.stack)
}
var rows []map[string]interface{}
for _, s := range stacks {
var latestEvent api.StackEvent
if len(s.Events) > 0 {
sort.Slice(s.Events, func(i, j int) bool {
return s.Events[i].UpdatedAt.Before(s.Events[j].UpdatedAt)
})
latestEvent = s.Events[len(s.Events)-1]
}
var desc string
if latestEvent.Description != nil {
desc = *latestEvent.Description
}
row := map[string]interface{}{
"ID": s.Id,
"OrgID": s.OrgID,
"Active": latestEvent.EventType != "uninstall",
"Name": latestEvent.Name,
"Description": desc,
"Num Resources": len(latestEvent.Resources),
"Sources": latestEvent.Sources,
"URLs": latestEvent.Urls,
"Created At": s.CreatedAt,
"Updated At": latestEvent.UpdatedAt,
}
rows = append(rows, row)
}
return c.PrintTable(headers, rows...)
}

View File

@ -4,11 +4,11 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/influxdata/influx-cli/v2/clients/export"
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
"github.com/influxdata/influx-cli/v2/pkg/template"
"github.com/mattn/go-isatty"
"github.com/urfave/cli"
)
@ -205,7 +205,8 @@ https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/`,
},
}
outParams, closer, err := parseOutParams(params.out)
cli := getCLI(ctx)
outParams, closer, err := template.ParseOutParams(params.out, cli.StdIO)
if closer != nil {
defer closer()
}
@ -241,7 +242,7 @@ https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/`,
}
client := export.Client{
CLI: getCLI(ctx),
CLI: cli,
TemplatesApi: getAPI(ctx).TemplatesApi,
}
return client.Export(getContext(ctx), &parsedParams)
@ -338,7 +339,8 @@ https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/all/
}
}
outParams, closer, err := parseOutParams(params.out)
cli := getCLI(ctx)
outParams, closer, err := template.ParseOutParams(params.out, cli.StdIO)
if closer != nil {
defer closer()
}
@ -349,7 +351,7 @@ https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/all/
apiClient := getAPI(ctx)
client := export.Client{
CLI: getCLI(ctx),
CLI: cli,
TemplatesApi: apiClient.TemplatesApi,
OrganizationsApi: apiClient.OrganizationsApi,
}
@ -396,7 +398,8 @@ https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/stack/
StackId: ctx.Args().Get(0),
}
outParams, closer, err := parseOutParams(params.out)
cli := getCLI(ctx)
outParams, closer, err := template.ParseOutParams(params.out, cli.StdIO)
if closer != nil {
defer closer()
}
@ -407,7 +410,7 @@ https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/stack/
apiClient := getAPI(ctx)
client := export.Client{
CLI: getCLI(ctx),
CLI: cli,
TemplatesApi: apiClient.TemplatesApi,
OrganizationsApi: apiClient.OrganizationsApi,
}
@ -422,23 +425,3 @@ func splitNonEmpty(s string) []string {
}
return strings.Split(s, ",")
}
func parseOutParams(outPath string) (export.OutParams, func(), error) {
if outPath == "" {
return export.OutParams{Out: os.Stdout, Encoding: export.YamlEncoding}, nil, nil
}
f, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return export.OutParams{}, nil, fmt.Errorf("failed to open output path %q: %w", outPath, err)
}
params := export.OutParams{Out: f}
switch filepath.Ext(outPath) {
case ".json":
params.Encoding = export.JsonEncoding
default:
params.Encoding = export.YamlEncoding
}
return params, func() { _ = f.Close() }, nil
}

View File

@ -51,6 +51,7 @@ var app = cli.App{
newV1SubCommand(),
newAuthCommand(),
newApplyCmd(),
newStacksCmd(),
},
Before: withContext(),
}

300
cmd/influx/stacks.go Normal file
View File

@ -0,0 +1,300 @@
package main
import (
"fmt"
"strings"
"github.com/influxdata/influx-cli/v2/clients/stacks"
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
"github.com/influxdata/influx-cli/v2/pkg/template"
"github.com/urfave/cli"
)
func newStacksCmd() cli.Command {
var params stacks.ListParams
return cli.Command{
Name: "stacks",
Usage: "List stack(s) and associated templates. Subcommands manage stacks.",
Description: `List stack(s) and associated templates. Subcommands manage stacks.
Examples:
# list all known stacks
influx stacks
# list stacks filtered by stack name
# output here are stacks that have match at least 1 name provided
influx stacks --stack-name=$STACK_NAME_1 --stack-name=$STACK_NAME_2
# list stacks filtered by stack id
# output here are stacks that have match at least 1 ids provided
influx stacks --stack-id=$STACK_ID_1 --stack-id=$STACK_ID_2
# list stacks filtered by stack id or stack name
# output here are stacks that have match the id provided or
# matches of the name provided
influx stacks --stack-id=$STACK_ID --stack-name=$STACK_NAME
For information about Stacks and how they integrate with InfluxDB templates, see
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/stacks/`,
Before: middleware.WithBeforeFns(withCli(), withApi(true)),
Flags: append(
commonFlags(),
&cli.StringFlag{
Name: "org-id",
Usage: "The ID of the organization",
EnvVar: "INFLUX_ORG_ID",
Destination: &params.OrgId,
},
&cli.StringFlag{
Name: "org, o",
Usage: "The name of the organization",
EnvVar: "INFLUX_ORG",
Destination: &params.OrgName,
},
&cli.StringSliceFlag{
Name: "stack-id",
Usage: "Stack ID to filter by",
},
&cli.StringSliceFlag{
Name: "stack-name",
Usage: "Stack name to filter by",
},
),
Subcommands: []cli.Command{
newStacksInitCmd(),
newStacksRemoveCmd(),
newStacksUpdateCmd(),
},
Action: func(ctx *cli.Context) error {
params.StackIds = ctx.StringSlice("stack-id")
params.StackNames = ctx.StringSlice("stack-name")
api := getAPI(ctx)
client := stacks.Client{
CLI: getCLI(ctx),
StacksApi: api.StacksApi,
OrganizationsApi: api.OrganizationsApi,
}
return client.List(getContext(ctx), &params)
},
}
}
func newStacksInitCmd() cli.Command {
var params stacks.InitParams
return cli.Command{
Name: "init",
Usage: "Initialize a stack",
Description: `The stack init command creates a new stack to associated templates with. A
stack is used to make applying templates idempotent. When you apply a template
and associate it with a stack, the stack can manage the created/updated resources
from the template back to the platform. This enables a multitude of useful features.
Any associated template urls will be applied when applying templates via a stack.
Examples:
# Initialize a stack with a name and description
influx stacks init -n $STACK_NAME -d $STACK_DESCRIPTION
# Initialize a stack with a name and urls to associate with stack.
influx stacks init -n $STACK_NAME -u $PATH_TO_TEMPLATE
For information about how stacks work with InfluxDB templates, see
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/stacks/
and
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/stacks/init/`,
Before: middleware.WithBeforeFns(withCli(), withApi(true)),
Flags: append(
commonFlags(),
&cli.StringFlag{
Name: "org-id",
Usage: "The ID of the organization",
EnvVar: "INFLUX_ORG_ID",
Destination: &params.OrgId,
},
&cli.StringFlag{
Name: "org, o",
Usage: "The name of the organization",
EnvVar: "INFLUX_ORG",
Destination: &params.OrgName,
},
&cli.StringFlag{
Name: "stack-name, n",
Usage: "Name given to created stack",
Destination: &params.Name,
},
&cli.StringFlag{
Name: "stack-description, d",
Usage: "Description given to created stack",
Destination: &params.Description,
},
&cli.StringSliceFlag{
Name: "template-url, u",
Usage: "Template urls to associate with new stack",
},
),
Action: func(ctx *cli.Context) error {
params.URLs = ctx.StringSlice("template-url")
api := getAPI(ctx)
client := stacks.Client{
CLI: getCLI(ctx),
StacksApi: api.StacksApi,
OrganizationsApi: api.OrganizationsApi,
}
return client.Init(getContext(ctx), &params)
},
}
}
func newStacksRemoveCmd() cli.Command {
var params stacks.RemoveParams
return cli.Command{
Name: "rm",
Aliases: []string{"remove", "uninstall"},
Usage: "Remove a stack(s) and all associated resources",
Before: middleware.WithBeforeFns(withCli(), withApi(true)),
Flags: append(
commonFlags(),
&cli.StringFlag{
Name: "org-id",
Usage: "The ID of the organization",
EnvVar: "INFLUX_ORG_ID",
Destination: &params.OrgId,
},
&cli.StringFlag{
Name: "org, o",
Usage: "The name of the organization",
EnvVar: "INFLUX_ORG",
Destination: &params.OrgName,
},
&cli.StringSliceFlag{
Name: "stack-id",
Usage: "Stack IDs to be removed",
Required: true,
},
&cli.BoolFlag{
Name: "force",
Usage: "Remove stack without confirmation prompt",
Destination: &params.Force,
},
),
Action: func(ctx *cli.Context) error {
params.Ids = ctx.StringSlice("stack-id")
api := getAPI(ctx)
client := stacks.Client{
CLI: getCLI(ctx),
StacksApi: api.StacksApi,
OrganizationsApi: api.OrganizationsApi,
}
return client.Remove(getContext(ctx), &params)
},
}
}
func newStacksUpdateCmd() cli.Command {
var params stacks.UpdateParams
return cli.Command{
Name: "update",
Usage: "Update a stack",
Description: `The stack update command updates a stack.
Examples:
# Update a stack with a name and description
influx stacks update -i $STACK_ID -n $STACK_NAME -d $STACK_DESCRIPTION
# Update a stack with a name and urls to associate with stack.
influx stacks update --stack-id $STACK_ID --stack-name $STACK_NAME --template-url $PATH_TO_TEMPLATE
# Update stack with new resources to manage
influx stacks update \
--stack-id $STACK_ID \
--addResource=Bucket=$BUCKET_ID \
--addResource=Dashboard=$DASH_ID
# Update stack with new resources to manage and export stack
# as a template
influx stacks update \
--stack-id $STACK_ID \
--addResource=Bucket=$BUCKET_ID \
--export-file /path/to/file.yml
For information about how stacks work with InfluxDB templates, see
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/stacks/
and
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/stacks/update/
`,
Before: middleware.WithBeforeFns(withCli(), withApi(true)),
Flags: append(
commonFlags(),
&cli.StringFlag{
Name: "stack-id, i",
Usage: "ID of stack",
Destination: &params.Id,
Required: true,
},
cli.StringFlag{
Name: "stack-name, n",
Usage: "New name for the stack",
},
cli.StringFlag{
Name: "stack-description, d",
Usage: "New description for the stack",
},
cli.StringSliceFlag{
Name: "template-url, u",
Usage: "New template URLs to associate with the stack",
},
cli.StringSliceFlag{
Name: "addResource",
Usage: "Additional resources to associate with the stack",
},
cli.StringFlag{
Name: "export-file, f",
Usage: "Destination for exported template",
TakesFile: true,
},
),
Action: func(ctx *cli.Context) error {
if ctx.IsSet("stack-name") {
name := ctx.String("stack-name")
params.Name = &name
}
if ctx.IsSet("stack-description") {
desc := ctx.String("stack-description")
params.Description = &desc
}
if ctx.IsSet("template-url") {
urls := ctx.StringSlice("template-url")
params.URLs = urls
}
rawResources := ctx.StringSlice("addResource")
for _, res := range rawResources {
pieces := strings.Split(res, "=")
if len(pieces) != 2 {
return fmt.Errorf("invalid resource specification %q, must have format `KIND=ID`", res)
}
params.AddedResources = append(params.AddedResources, stacks.AddedResource{
Kind: pieces[0],
Id: pieces[1],
})
}
cli := getCLI(ctx)
outParams, closer, err := template.ParseOutParams(ctx.String("export-file"), cli.StdIO)
if closer != nil {
defer closer()
}
if err != nil {
return err
}
params.OutParams = outParams
api := getAPI(ctx)
client := stacks.Client{
CLI: cli,
StacksApi: api.StacksApi,
TemplatesApi: api.TemplatesApi,
}
return client.Update(getContext(ctx), &params)
},
}
}

63
pkg/template/out.go Normal file
View File

@ -0,0 +1,63 @@
package template
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"github.com/influxdata/influx-cli/v2/api"
"gopkg.in/yaml.v3"
)
type OutEncoding int
const (
YamlEncoding OutEncoding = iota
JsonEncoding
)
type OutParams struct {
Out io.Writer
Encoding OutEncoding
}
func ParseOutParams(path string, fallback io.Writer) (OutParams, func(), error) {
if path == "" {
return OutParams{Out: fallback, Encoding: YamlEncoding}, nil, nil
}
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return OutParams{}, nil, fmt.Errorf("failed to open output path %q: %w", path, err)
}
params := OutParams{Out: f}
switch filepath.Ext(path) {
case ".json":
params.Encoding = JsonEncoding
default:
params.Encoding = YamlEncoding
}
return params, func() { _ = f.Close() }, nil
}
func (o OutParams) WriteTemplate(template []api.TemplateEntry) error {
switch o.Encoding {
case JsonEncoding:
enc := json.NewEncoder(o.Out)
enc.SetIndent("", "\t")
return enc.Encode(template)
case YamlEncoding:
enc := yaml.NewEncoder(o.Out)
for _, entry := range template {
if err := enc.Encode(entry); err != nil {
return err
}
}
default:
return fmt.Errorf("encoding %q is not recognized", o.Encoding)
}
return nil
}

124
pkg/template/out_test.go Normal file
View File

@ -0,0 +1,124 @@
package template_test
import (
"bytes"
"encoding/json"
"io"
"os"
"path/filepath"
"testing"
"github.com/influxdata/influx-cli/v2/api"
"github.com/influxdata/influx-cli/v2/pkg/template"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
var tmpls = []api.TemplateEntry{
{
ApiVersion: "api1",
Kind: "Foo",
Metadata: api.TemplateEntryMetadata{
Name: "foo",
},
Spec: map[string]interface{}{
"hello": "world",
"1 + 1 =": "2",
},
},
{
ApiVersion: "api1",
Kind: "Bar",
Metadata: api.TemplateEntryMetadata{
Name: "bar",
},
Spec: map[string]interface{}{
"success?": "true",
},
},
}
func TestOutParams(t *testing.T) {
t.Parallel()
t.Run("json to file", func(t *testing.T) {
t.Parallel()
tmp, err := os.MkdirTemp("", "")
require.NoError(t, err)
defer os.RemoveAll(tmp)
out := filepath.Join(tmp, "test.json")
params, closer, err := template.ParseOutParams(out, nil)
require.NoError(t, err)
require.NotNil(t, closer)
defer closer()
require.NoError(t, params.WriteTemplate(tmpls))
contents, err := os.ReadFile(out)
require.NoError(t, err)
var written []api.TemplateEntry
dec := json.NewDecoder(bytes.NewReader(contents))
require.NoError(t, dec.Decode(&written))
require.Equal(t, tmpls, written)
})
t.Run("yaml to file", func(t *testing.T) {
t.Parallel()
tmp, err := os.MkdirTemp("", "")
require.NoError(t, err)
defer os.RemoveAll(tmp)
out := filepath.Join(tmp, "test.yaml")
params, closer, err := template.ParseOutParams(out, nil)
require.NoError(t, err)
require.NotNil(t, closer)
defer closer()
require.NoError(t, params.WriteTemplate(tmpls))
contents, err := os.ReadFile(out)
require.NoError(t, err)
var written []api.TemplateEntry
dec := yaml.NewDecoder(bytes.NewReader(contents))
for {
var e api.TemplateEntry
err := dec.Decode(&e)
if err == io.EOF {
break
}
require.NoError(t, err)
written = append(written, e)
}
require.Equal(t, tmpls, written)
})
t.Run("yaml to buffer", func(t *testing.T) {
t.Parallel()
out := bytes.Buffer{}
params, closer, err := template.ParseOutParams("", &out)
require.NoError(t, err)
require.Nil(t, closer)
require.NoError(t, params.WriteTemplate(tmpls))
var written []api.TemplateEntry
dec := yaml.NewDecoder(&out)
for {
var e api.TemplateEntry
err := dec.Decode(&e)
if err == io.EOF {
break
}
require.NoError(t, err)
written = append(written, e)
}
require.Equal(t, tmpls, written)
})
}