feat: add --version flag to coder templates pull, default to active version (#10153)

Fixes https://github.com/coder/coder/issues/9837
This commit is contained in:
Colin Adler
2023-10-10 10:20:31 -05:00
committed by GitHub
parent 2506415def
commit c11f241622
8 changed files with 213 additions and 32 deletions
+58 -21
View File
@@ -15,12 +15,15 @@ import (
)
func (r *RootCmd) templatePull() *clibase.Cmd {
var tarMode bool
var (
tarMode bool
versionName string
)
client := new(codersdk.Client)
cmd := &clibase.Cmd{
Use: "pull <name> [destination]",
Short: "Download the latest version of a template to a path.",
Short: "Download the active, latest, or specified version of a template to a path.",
Middleware: clibase.Chain(
clibase.RequireRangeArgs(1, 2),
r.InitClient(client),
@@ -36,39 +39,67 @@ func (r *RootCmd) templatePull() *clibase.Cmd {
dest = inv.Args[1]
}
// TODO(JonA): Do we need to add a flag for organization?
organization, err := CurrentOrganization(inv, client)
if err != nil {
return xerrors.Errorf("current organization: %w", err)
return xerrors.Errorf("get current organization: %w", err)
}
template, err := client.TemplateByName(ctx, organization.ID, templateName)
if err != nil {
return xerrors.Errorf("template by name: %w", err)
return xerrors.Errorf("get template by name: %w", err)
}
// Pull the versions for the template. We'll find the latest
// one and download the source.
versions, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
TemplateID: template.ID,
})
if err != nil {
return xerrors.Errorf("template versions by template: %w", err)
var latestVersion codersdk.TemplateVersion
{
// Determine the latest template version and compare with the
// active version. If they aren't the same, warn the user.
versions, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
TemplateID: template.ID,
})
if err != nil {
return xerrors.Errorf("template versions by template: %w", err)
}
if len(versions) == 0 {
return xerrors.Errorf("no template versions for template %q", templateName)
}
// Sort the slice from newest to oldest template.
sort.SliceStable(versions, func(i, j int) bool {
return versions[i].CreatedAt.After(versions[j].CreatedAt)
})
latestVersion = versions[0]
}
if len(versions) == 0 {
return xerrors.Errorf("no template versions for template %q", templateName)
var templateVersion codersdk.TemplateVersion
switch versionName {
case "", "active":
activeVersion, err := client.TemplateVersion(ctx, template.ActiveVersionID)
if err != nil {
return xerrors.Errorf("get active template version: %w", err)
}
if versionName == "" && activeVersion.ID != latestVersion.ID {
cliui.Warn(inv.Stderr,
"A newer template version than the active version exists. Pulling the active version instead.",
"Use "+cliui.Code("--template latest")+" to pull the latest version.",
)
}
templateVersion = activeVersion
case "latest":
templateVersion = latestVersion
default:
version, err := client.TemplateVersionByName(ctx, template.ID, versionName)
if err != nil {
return xerrors.Errorf("get template version: %w", err)
}
templateVersion = version
}
// Sort the slice from newest to oldest template.
sort.SliceStable(versions, func(i, j int) bool {
return versions[i].CreatedAt.After(versions[j].CreatedAt)
})
latest := versions[0]
cliui.Info(inv.Stderr, "Pulling template version "+cliui.Bold(templateVersion.Name)+"...")
// Download the tar archive.
raw, ctype, err := client.Download(ctx, latest.Job.FileID)
raw, ctype, err := client.Download(ctx, templateVersion.Job.FileID)
if err != nil {
return xerrors.Errorf("download template: %w", err)
}
@@ -121,6 +152,12 @@ func (r *RootCmd) templatePull() *clibase.Cmd {
Value: clibase.BoolOf(&tarMode),
},
{
Description: "The name of the template version to pull. Use 'active' to pull the active version, 'latest' to pull the latest version, or the name of the template version to pull.",
Flag: "version",
Value: clibase.StringOf(&versionName),
},
cliui.SkipPromptOption(),
}
+126 -4
View File
@@ -7,6 +7,7 @@ import (
"encoding/hex"
"os"
"path/filepath"
"strings"
"testing"
"github.com/codeclysm/extract/v3"
@@ -49,7 +50,7 @@ func TestTemplatePull_NoName(t *testing.T) {
require.Error(t, err)
}
// Stdout tests that 'templates pull' pulls down the latest template
// Stdout tests that 'templates pull' pulls down the active template
// and writes it to stdout.
func TestTemplatePull_Stdout(t *testing.T) {
t.Parallel()
@@ -78,6 +79,7 @@ func TestTemplatePull_Stdout(t *testing.T) {
// are being sorted correctly.
updatedVersion := coderdtest.UpdateTemplateVersion(t, client, owner.OrganizationID, source2, template.ID)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, updatedVersion.ID)
coderdtest.UpdateActiveTemplateVersion(t, client, template.ID, updatedVersion.ID)
inv, root := clitest.New(t, "templates", "pull", "--tar", template.Name)
clitest.SetupConfig(t, templateAdmin, root)
@@ -91,7 +93,123 @@ func TestTemplatePull_Stdout(t *testing.T) {
require.True(t, bytes.Equal(expected, buf.Bytes()), "tar files differ")
}
// ToDir tests that 'templates pull' pulls down the latest template
// Stdout tests that 'templates pull' pulls down the non-latest active template
// and writes it to stdout.
func TestTemplatePull_ActiveOldStdout(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
})
user := coderdtest.CreateFirstUser(t, client)
source1 := genTemplateVersionSource()
source2 := genTemplateVersionSource()
expected, err := echo.Tar(source1)
require.NoError(t, err)
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, source1)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version1.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version1.ID)
updatedVersion := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, source2, template.ID)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, updatedVersion.ID)
inv, root := clitest.New(t, "templates", "pull", "--tar", template.Name)
clitest.SetupConfig(t, client, root)
var buf bytes.Buffer
inv.Stdout = &buf
var stderr strings.Builder
inv.Stderr = &stderr
err = inv.Run()
require.NoError(t, err)
require.True(t, bytes.Equal(expected, buf.Bytes()), "tar files differ")
require.Contains(t, stderr.String(), "A newer template version than the active version exists.")
}
// Stdout tests that 'templates pull' pulls down the specified template and
// writes it to stdout.
func TestTemplatePull_SpecifiedStdout(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
})
user := coderdtest.CreateFirstUser(t, client)
source1 := genTemplateVersionSource()
source2 := genTemplateVersionSource()
source3 := genTemplateVersionSource()
expected, err := echo.Tar(source1)
require.NoError(t, err)
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, source1)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version1.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version1.ID)
updatedVersion := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, source2, template.ID)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, updatedVersion.ID)
updatedVersion2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, source3, template.ID)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, updatedVersion2.ID)
coderdtest.UpdateActiveTemplateVersion(t, client, template.ID, updatedVersion2.ID)
inv, root := clitest.New(t, "templates", "pull", "--tar", template.Name, "--version", version1.Name)
clitest.SetupConfig(t, client, root)
var buf bytes.Buffer
inv.Stdout = &buf
err = inv.Run()
require.NoError(t, err)
require.True(t, bytes.Equal(expected, buf.Bytes()), "tar files differ")
}
// Stdout tests that 'templates pull' pulls down the latest template
// and writes it to stdout.
func TestTemplatePull_LatestStdout(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
})
user := coderdtest.CreateFirstUser(t, client)
source1 := genTemplateVersionSource()
source2 := genTemplateVersionSource()
expected, err := echo.Tar(source1)
require.NoError(t, err)
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, source1)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version1.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version1.ID)
updatedVersion := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, source2, template.ID)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, updatedVersion.ID)
inv, root := clitest.New(t, "templates", "pull", "--tar", template.Name, "latest")
clitest.SetupConfig(t, client, root)
var buf bytes.Buffer
inv.Stdout = &buf
err = inv.Run()
require.NoError(t, err)
require.True(t, bytes.Equal(expected, buf.Bytes()), "tar files differ")
}
// ToDir tests that 'templates pull' pulls down the active template
// and writes it to the correct directory.
func TestTemplatePull_ToDir(t *testing.T) {
t.Parallel()
@@ -120,6 +238,7 @@ func TestTemplatePull_ToDir(t *testing.T) {
// are being sorted correctly.
updatedVersion := coderdtest.UpdateTemplateVersion(t, client, owner.OrganizationID, source2, template.ID)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, updatedVersion.ID)
coderdtest.UpdateActiveTemplateVersion(t, client, template.ID, updatedVersion.ID)
dir := t.TempDir()
@@ -143,8 +262,9 @@ func TestTemplatePull_ToDir(t *testing.T) {
)
}
// ToDir tests that 'templates pull' pulls down the latest template
// and writes it to a directory with the name of the template if the path is not implicitly supplied.
// ToDir tests that 'templates pull' pulls down the active template and writes
// it to a directory with the name of the template if the path is not implicitly
// supplied.
// nolint: paralleltest
func TestTemplatePull_ToImplicit(t *testing.T) {
client := coderdtest.New(t, &coderdtest.Options{
@@ -171,6 +291,7 @@ func TestTemplatePull_ToImplicit(t *testing.T) {
// are being sorted correctly.
updatedVersion := coderdtest.UpdateTemplateVersion(t, client, owner.OrganizationID, source2, template.ID)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, updatedVersion.ID)
coderdtest.UpdateActiveTemplateVersion(t, client, template.ID, updatedVersion.ID)
// create a tempdir and change the working directory to it for the duration of the test (cannot run in parallel)
dir := t.TempDir()
@@ -233,6 +354,7 @@ func TestTemplatePull_FolderConflict(t *testing.T) {
// are being sorted correctly.
updatedVersion := coderdtest.UpdateTemplateVersion(t, client, owner.OrganizationID, source2, template.ID)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, updatedVersion.ID)
coderdtest.UpdateActiveTemplateVersion(t, client, template.ID, updatedVersion.ID)
dir := t.TempDir()
+2 -1
View File
@@ -29,7 +29,8 @@ SUBCOMMANDS:
edit Edit the metadata of a template by name.
init Get started with a templated template.
list List all the templates available for the organization
pull Download the latest version of a template to a path.
pull Download the active, latest, or specified version of a template
to a path.
push Push a new template version from the current directory or as
specified by flag
versions Manage different versions of the specified template
+6 -1
View File
@@ -3,12 +3,17 @@ coder v0.0.0-devel
USAGE:
coder templates pull [flags] <name> [destination]
Download the latest version of a template to a path.
Download the active, latest, or specified version of a template to a path.
OPTIONS:
--tar bool
Output the template as a tar archive to stdout.
--version string
The name of the template version to pull. Use 'active' to pull the
active version, 'latest' to pull the latest version, or the name of
the template version to pull.
-y, --yes bool
Bypass prompts.
+10 -2
View File
@@ -753,11 +753,12 @@ func CreateTemplate(t *testing.T, client *codersdk.Client, organization uuid.UUI
// UpdateTemplateVersion creates a new template version with the "echo" provisioner
// and associates it with the given templateID.
func UpdateTemplateVersion(t *testing.T, client *codersdk.Client, organizationID uuid.UUID, res *echo.Responses, templateID uuid.UUID) codersdk.TemplateVersion {
ctx := context.Background()
data, err := echo.Tar(res)
require.NoError(t, err)
file, err := client.Upload(context.Background(), codersdk.ContentTypeTar, bytes.NewReader(data))
file, err := client.Upload(ctx, codersdk.ContentTypeTar, bytes.NewReader(data))
require.NoError(t, err)
templateVersion, err := client.CreateTemplateVersion(context.Background(), organizationID, codersdk.CreateTemplateVersionRequest{
templateVersion, err := client.CreateTemplateVersion(ctx, organizationID, codersdk.CreateTemplateVersionRequest{
TemplateID: templateID,
FileID: file.ID,
StorageMethod: codersdk.ProvisionerStorageMethodFile,
@@ -767,6 +768,13 @@ func UpdateTemplateVersion(t *testing.T, client *codersdk.Client, organizationID
return templateVersion
}
func UpdateActiveTemplateVersion(t *testing.T, client *codersdk.Client, templateID, versionID uuid.UUID) {
err := client.UpdateActiveTemplateVersion(context.Background(), templateID, codersdk.UpdateActiveTemplateVersion{
ID: versionID,
})
require.NoError(t, err)
}
// AwaitTemplateVersionJobRunning waits for the build to be picked up by a provisioner.
func AwaitTemplateVersionJobRunning(t *testing.T, client *codersdk.Client, version uuid.UUID) codersdk.TemplateVersion {
t.Helper()
+1 -1
View File
@@ -40,6 +40,6 @@ Templates are written in standard Terraform and describe the infrastructure for
| [<code>edit</code>](./templates_edit.md) | Edit the metadata of a template by name. |
| [<code>init</code>](./templates_init.md) | Get started with a templated template. |
| [<code>list</code>](./templates_list.md) | List all the templates available for the organization |
| [<code>pull</code>](./templates_pull.md) | Download the latest version of a template to a path. |
| [<code>pull</code>](./templates_pull.md) | Download the active, latest, or specified version of a template to a path. |
| [<code>push</code>](./templates_push.md) | Push a new template version from the current directory or as specified by flag |
| [<code>versions</code>](./templates_versions.md) | Manage different versions of the specified template |
+9 -1
View File
@@ -2,7 +2,7 @@
# templates pull
Download the latest version of a template to a path.
Download the active, latest, or specified version of a template to a path.
## Usage
@@ -20,6 +20,14 @@ coder templates pull [flags] <name> [destination]
Output the template as a tar archive to stdout.
### --version
| | |
| ---- | ------------------- |
| Type | <code>string</code> |
The name of the template version to pull. Use 'active' to pull the active version, 'latest' to pull the latest version, or the name of the template version to pull.
### -y, --yes
| | |
+1 -1
View File
@@ -838,7 +838,7 @@
},
{
"title": "templates pull",
"description": "Download the latest version of a template to a path.",
"description": "Download the active, latest, or specified version of a template to a path.",
"path": "cli/templates_pull.md"
},
{