Skip to content

Commit

Permalink
Merge pull request #36 from hashicorp/WAYP-2335-waypoint-update-templ…
Browse files Browse the repository at this point in the history
…ates

[WAYP-2335] `waypoint templates update` command
  • Loading branch information
paladin-devops authored Mar 20, 2024
2 parents eca6da4 + deb1a02 commit eb4bfcf
Show file tree
Hide file tree
Showing 3 changed files with 320 additions and 2 deletions.
12 changes: 10 additions & 2 deletions internal/commands/waypoint/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@ import (
type TemplateOpts struct {
opts.WaypointOpts

ID string
Name string
ID string

// Name is the name of a new template, or the name of an existing template.
// When used during update operations, it is the name of the template to be
// updated.
Name string

// UpdatedName is used for updates, and is the new name for the template.
UpdatedName string
Summary string
Description string
ReadmeMarkdownTemplateFile string
Expand Down Expand Up @@ -45,6 +52,7 @@ func NewCmdTemplate(ctx *cmd.Context) *cmd.Command {
cmd.AddChild(NewCmdDelete(ctx, opts))
cmd.AddChild(NewCmdRead(ctx, opts))
cmd.AddChild(NewCmdList(ctx, opts))
cmd.AddChild(NewCmdUpdate(ctx, opts))

return cmd
}
116 changes: 116 additions & 0 deletions internal/commands/waypoint/template/template_update_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package template

import (
"context"
"testing"

"github.com/go-openapi/runtime/client"
"github.com/hashicorp/hcp/internal/pkg/cmd"
"github.com/hashicorp/hcp/internal/pkg/format"
"github.com/hashicorp/hcp/internal/pkg/iostreams"
"github.com/hashicorp/hcp/internal/pkg/profile"
"github.com/stretchr/testify/require"
)

func TestCmdTemplateUpdate(t *testing.T) {
t.Parallel()

cases := []struct {
Name string
Args []string
Profile func(t *testing.T) *profile.Profile
Error string
Expect *TemplateOpts
}{
{
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured",
},
{
Name: "no args",
Profile: func(t *testing.T) *profile.Profile {
return profile.TestProfile(t).SetOrgID("123")
},
Args: []string{},
Error: "accepts 1 arg(s), received 0",
},
{
Name: "happy",
Profile: func(t *testing.T) *profile.Profile {
return profile.TestProfile(t).SetOrgID("123")
},
Args: []string{
"-n=cli-test",
"--new-name=cli-test-new",
"-s", "A template created using the CLI.",
"--tfc-project-id", "prj-abcdefghij",
"--tfc-project-name", "test",
"--tfc-no-code-module-source", "private/waypoint/waypoint-nocode-module/null",
"--tfc-no-code-module-version", "0.0.1",
"-l", "cli",
"-d", "A template created with the CLI.",
"-t", "cli=true",
"--readme-markdown-template-file", "readme_test.txt",
},
Expect: &TemplateOpts{
Name: "cli-test",
UpdatedName: "cli-test-new",
Summary: "A template created using the CLI.",
Description: "A template created with the CLI.",
TerraformCloudProjectID: "prj-abcdefghij",
TerraformCloudProjectName: "test",
TerraformNoCodeModuleSource: "private/waypoint/waypoint-nocode-module/null",
TerraformNoCodeModuleVersion: "0.0.1",
ReadmeMarkdownTemplateFile: "readme_test.txt",
Labels: []string{"cli"},
Tags: map[string]string{"cli": "true"},
},
},
}

for _, c := range cases {
c := c
t.Run(c.Name, func(t *testing.T) {
t.Parallel()

r := require.New(t)

// Create a context.
io := iostreams.Test()
ctx := &cmd.Context{
IO: io,
Profile: c.Profile(t),
Output: format.New(io),
HCP: &client.Runtime{},
ShutdownCtx: context.Background(),
}

var tplOpts TemplateOpts
tplOpts.testFunc = func(c *cmd.Command, args []string) error {
return nil
}
cmd := NewCmdUpdate(ctx, &tplOpts)
cmd.SetIO(io)

cmd.Run(c.Args)

if c.Expect != nil {
r.NotNil(c.Expect)

r.Equal(c.Expect.Name, tplOpts.Name)
r.Equal(c.Expect.UpdatedName, tplOpts.UpdatedName)
r.Equal(c.Expect.Description, tplOpts.Description)
r.Equal(c.Expect.Summary, tplOpts.Summary)
r.Equal(c.Expect.TerraformCloudProjectID, tplOpts.TerraformCloudProjectID)
r.Equal(c.Expect.TerraformCloudProjectName, tplOpts.TerraformCloudProjectName)
r.Equal(c.Expect.TerraformNoCodeModuleSource, tplOpts.TerraformNoCodeModuleSource)
r.Equal(c.Expect.TerraformNoCodeModuleVersion, tplOpts.TerraformNoCodeModuleVersion)
r.Equal(c.Expect.ReadmeMarkdownTemplateFile, tplOpts.ReadmeMarkdownTemplateFile)
r.Equal(c.Expect.Labels, tplOpts.Labels)
r.Equal(c.Expect.Tags, tplOpts.Tags)
}
})
}
}
194 changes: 194 additions & 0 deletions internal/commands/waypoint/template/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package template

import (
"fmt"
"os"

"github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2023-08-18/client/waypoint_service"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2023-08-18/models"
"github.com/hashicorp/hcp/internal/pkg/cmd"
"github.com/hashicorp/hcp/internal/pkg/flagvalue"
"github.com/hashicorp/hcp/internal/pkg/heredoc"
"github.com/pkg/errors"
)

func NewCmdUpdate(ctx *cmd.Context, opts *TemplateOpts) *cmd.Command {
cmd := &cmd.Command{
Name: "update",
ShortHelp: "Update an existing HCP Waypoint template.",
LongHelp: "Update an existing HCP Waypoint template. This will update" +
" the template with the provided information.",
Examples: []cmd.Example{
{
Preamble: "Create a new HCP Waypoint template:",
Command: heredoc.New(ctx.IO, heredoc.WithPreserveNewlines()).Must(`
$ hcp waypoint templates update -n my-template \
-s "My Template Summary" \
-d "My Template Description" \
-readme-markdown-template-file "README.tpl" \
-tfc-no-code-module-source "app.terraform.io/hashicorp/dir/template" \
-tfc-no-code-module-version "1.0.2" \
-tfc-project-name "my-tfc-project" \
-tfc-project-id "prj-123456 \
-l "label1" \
-l "label2"
`),
},
},
RunF: func(c *cmd.Command, args []string) error {
if opts.testFunc != nil {
return opts.testFunc(c, args)
}
return templateUpdate(opts)
},
PersistentPreRun: func(c *cmd.Command, args []string) error {
return cmd.RequireOrgAndProject(ctx)
},
Flags: cmd.Flags{
Local: []*cmd.Flag{
{
Name: "name",
Shorthand: "n",
DisplayValue: "NAME",
Description: "The name of the template to be updated.",
Value: flagvalue.Simple("", &opts.Name),
Required: true,
},
{
Name: "new-name",
DisplayValue: "NEW_NAME",
Description: "The new name of the template.",
Value: flagvalue.Simple("", &opts.UpdatedName),
Hidden: true,
},
{
Name: "summary",
Shorthand: "s",
DisplayValue: "SUMMARY",
Description: "The summary of the template.",
Value: flagvalue.Simple("", &opts.Summary),
},
{
Name: "description",
Shorthand: "d",
DisplayValue: "DESCRIPTION",
Description: "The description of the template.",
Value: flagvalue.Simple("", &opts.Description),
},
{
Name: "readme-markdown-template-file",
DisplayValue: "README_MARKDOWN_TEMPLATE_FILE_PATH",
Description: "The file containing the README markdown template.",
Value: flagvalue.Simple("", &opts.ReadmeMarkdownTemplateFile),
},
{
Name: "label",
Shorthand: "l",
DisplayValue: "LABEL",
Description: "A label to apply to the template.",
Repeatable: true,
Value: flagvalue.SimpleSlice(nil, &opts.Labels),
},
{
Name: "tag",
Shorthand: "t",
DisplayValue: "KEY=VALUE",
Description: "A tag to apply to the template.",
Repeatable: true,
Value: flagvalue.SimpleMap(nil, &opts.Tags),
Hidden: true,
},
{
Name: "tfc-no-code-module-source",
DisplayValue: "TFC_NO_CODE_MODULE_SOURCE",
Description: heredoc.New(ctx.IO).Must(`
The source of the Terraform no-code module.
The expected format is "NAMESPACE/NAME/PROVIDER". An
optional "HOSTNAME/" can be added at the beginning for
a private registry.
`),
Value: flagvalue.Simple("", &opts.TerraformNoCodeModuleSource),
},
{
Name: "tfc-no-code-module-version",
DisplayValue: "TFC_NO_CODE_MODULE_VERSION",
Description: "The version of the Terraform no-code module.",
Value: flagvalue.Simple("", &opts.TerraformNoCodeModuleVersion),
},
{
Name: "tfc-project-name",
DisplayValue: "TFC_PROJECT_NAME",
Description: "The name of the Terraform Cloud project where" +
" applications using this template will be created.",
Value: flagvalue.Simple("", &opts.TerraformCloudProjectName),
},
{
Name: "tfc-project-id",
DisplayValue: "TFC_PROJECT_ID",
Description: "The ID of the Terraform Cloud project where" +
" applications using this template will be created.",
Value: flagvalue.Simple("", &opts.TerraformCloudProjectID),
},
},
},
}
return cmd
}

func templateUpdate(opts *TemplateOpts) error {
ns, err := opts.Namespace()
if err != nil {
return err
}

var tags []*models.HashicorpCloudWaypointTag
for k, v := range opts.Tags {
tags = append(tags, &models.HashicorpCloudWaypointTag{
Key: k,
Value: v,
})
}

var readmeTpl []byte
if opts.ReadmeMarkdownTemplateFile != "" {
readmeTpl, err = os.ReadFile(opts.ReadmeMarkdownTemplateFile)
if err != nil {
return errors.Wrapf(err, "failed to read file %q", opts.ReadmeMarkdownTemplateFile)
}
}

updatedTpl := &models.HashicorpCloudWaypointApplicationTemplate{
Name: opts.UpdatedName,
Summary: opts.Summary,
Description: opts.Description,
ReadmeMarkdownTemplate: readmeTpl,
Labels: opts.Labels,
Tags: tags,
TerraformNocodeModule: &models.HashicorpCloudWaypointTerraformNocodeModule{
Source: opts.TerraformNoCodeModuleSource,
Version: opts.TerraformNoCodeModuleVersion,
},
TerraformCloudWorkspaceDetails: &models.HashicorpCloudWaypointTerraformCloudWorkspaceDetails{
Name: opts.TerraformCloudProjectName,
ProjectID: opts.TerraformCloudProjectID,
},
}

_, err = opts.WS.WaypointServiceUpdateApplicationTemplate2(
&waypoint_service.WaypointServiceUpdateApplicationTemplate2Params{
NamespaceID: ns.ID,
Context: opts.Ctx,
ExistingApplicationTemplateName: opts.Name,
Body: &models.HashicorpCloudWaypointWaypointServiceUpdateApplicationTemplateBody{
ApplicationTemplate: updatedTpl,
},
}, nil,
)
if err != nil {
return errors.Wrapf(err, "failed to update template %q", opts.Name)
}

fmt.Fprintf(opts.IO.Err(), "Template %q updated.", opts.ID)

return nil
}

0 comments on commit eb4bfcf

Please sign in to comment.