Skip to content

Commit 3102599

Browse files
committed
swap behaviors between coder templates pull with coder templates export
1 parent f716c09 commit 3102599

File tree

3 files changed

+146
-146
lines changed

3 files changed

+146
-146
lines changed

cli/templateexport.go

Lines changed: 91 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,123 @@
11
package cli
22

33
import (
4-
"archive/tar"
5-
"bytes"
64
"fmt"
7-
"io"
5+
"io/fs"
86
"os"
9-
"path/filepath"
7+
"sort"
108

119
"github.com/spf13/cobra"
1210
"golang.org/x/xerrors"
1311

1412
"github.com/coder/coder/cli/cliui"
13+
"github.com/coder/coder/codersdk"
1514
)
1615

17-
func tarBytesToTree(destination string, raw []byte) error {
18-
err := os.Mkdir(destination, 0700)
19-
20-
archiveReader := tar.NewReader(bytes.NewReader(raw))
21-
hdr, err := archiveReader.Next()
22-
for err != io.EOF {
23-
if hdr == nil { // some blog post indicated this could happen sometimes
24-
continue
25-
}
26-
filename := filepath.FromSlash(fmt.Sprintf("%s/%s", destination, hdr.Name))
27-
switch hdr.Typeflag {
28-
case tar.TypeDir:
29-
err = os.Mkdir(filename, 0700)
30-
if err != nil {
31-
return xerrors.Errorf("exporting archived directory: %w", err)
32-
}
33-
case tar.TypeReg:
34-
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0600)
35-
if err != nil {
36-
return xerrors.Errorf("unable to create archived file: %w", err)
37-
}
16+
func fetchTemplateArchiveBytes(cmd *cobra.Command, templateName string) ([]byte, error) {
17+
ctx := cmd.Context()
18+
client, err := createClient(cmd)
19+
if err != nil {
20+
return nil, xerrors.Errorf("create client: %w", err)
21+
}
3822

39-
_, err = io.Copy(f, archiveReader)
40-
if err != nil {
41-
f.Close() // is this necessary?
42-
return xerrors.Errorf("error writing archive file: %w", err)
43-
}
44-
f.Close()
45-
}
23+
// TODO(JonA): Do we need to add a flag for organization?
24+
organization, err := currentOrganization(cmd, client)
25+
if err != nil {
26+
return nil, xerrors.Errorf("current organization: %w", err)
27+
}
28+
29+
template, err := client.TemplateByName(ctx, organization.ID, templateName)
30+
if err != nil {
31+
return nil, xerrors.Errorf("template by name: %w", err)
32+
}
33+
34+
// Pull the versions for the template. We'll find the latest
35+
// one and download the source.
36+
versions, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
37+
TemplateID: template.ID,
38+
})
39+
if err != nil {
40+
return nil, xerrors.Errorf("template versions by template: %w", err)
41+
}
42+
43+
if len(versions) == 0 {
44+
return nil, xerrors.Errorf("no template versions for template %q", templateName)
45+
}
46+
47+
// Sort the slice from newest to oldest template.
48+
sort.SliceStable(versions, func(i, j int) bool {
49+
return versions[i].CreatedAt.After(versions[j].CreatedAt)
50+
})
51+
52+
latest := versions[0]
4653

47-
hdr, err = archiveReader.Next()
54+
// Download the tar archive.
55+
raw, ctype, err := client.Download(ctx, latest.Job.StorageSource)
56+
if err != nil {
57+
return nil, xerrors.Errorf("download template: %w", err)
4858
}
49-
return nil
59+
60+
if ctype != codersdk.ContentTypeTar {
61+
return nil, xerrors.Errorf("unexpected Content-Type %q, expecting %q", ctype, codersdk.ContentTypeTar)
62+
}
63+
return raw, nil
5064
}
5165

5266
func templateExport() *cobra.Command {
5367
cmd := &cobra.Command{
54-
Use: "export <template name> [destination]",
55-
Short: "Download the named template's contents into a subdirectory.",
56-
Long: "Download the named template's contents and extract them into a subdirectory named according to the destination or <template name> if no destination is specified.",
68+
Use: "export <name> [destination]",
69+
Short: "Download the latest version of a template.",
5770
Args: cobra.RangeArgs(1, 2),
5871
RunE: func(cmd *cobra.Command, args []string) error {
59-
templateName := args[0]
60-
var destination string
72+
raw, err := fetchTemplateArchiveBytes(cmd, args[0])
73+
if err != nil {
74+
return err
75+
}
76+
77+
var dest string
6178
if len(args) > 1 {
62-
destination = args[1]
63-
} else {
64-
destination = templateName
79+
dest = args[1]
6580
}
6681

67-
raw, err := fetchTemplateArchiveBytes(cmd, templateName)
68-
if err != nil {
69-
return err
82+
// If the destination is empty then we write to stdout
83+
// and bail early.
84+
if dest == "" {
85+
_, err = cmd.OutOrStdout().Write(raw)
86+
if err != nil {
87+
return xerrors.Errorf("write stdout: %w", err)
88+
}
89+
return nil
7090
}
7191

7292
// Stat the destination to ensure nothing exists already.
73-
stat, err := os.Stat(destination)
74-
if stat != nil {
75-
return xerrors.Errorf("template file/directory already exists: %s", destination)
93+
fi, err := os.Stat(dest)
94+
if err != nil && !xerrors.Is(err, fs.ErrNotExist) {
95+
return xerrors.Errorf("stat destination: %w", err)
96+
}
97+
98+
if fi != nil && fi.IsDir() {
99+
// If the destination is a directory we just bail.
100+
return xerrors.Errorf("%q already exists.", dest)
101+
}
102+
103+
// If a file exists at the destination prompt the user
104+
// to ensure we don't overwrite something valuable.
105+
if fi != nil {
106+
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
107+
Text: fmt.Sprintf("%q already exists, do you want to overwrite it?", dest),
108+
IsConfirm: true,
109+
})
110+
if err != nil {
111+
return xerrors.Errorf("parse prompt: %w", err)
112+
}
113+
}
114+
115+
err = os.WriteFile(dest, raw, 0600)
116+
if err != nil {
117+
return xerrors.Errorf("write to path: %w", err)
76118
}
77119

78-
return tarBytesToTree(destination, raw)
120+
return nil
79121
},
80122
}
81123

cli/templatepull_test.go renamed to cli/templateexport_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ import (
1616
"github.com/coder/coder/pty/ptytest"
1717
)
1818

19-
func TestTemplatePull(t *testing.T) {
19+
func TestTemplateExport(t *testing.T) {
2020
t.Parallel()
2121

2222
t.Run("NoName", func(t *testing.T) {
2323
t.Parallel()
2424

25-
cmd, _ := clitest.New(t, "templates", "pull")
25+
cmd, _ := clitest.New(t, "templates", "export")
2626
err := cmd.Execute()
2727
require.Error(t, err)
2828
})
2929

30-
// Stdout tests that 'templates pull' pulls down the latest template
30+
// Stdout tests that 'templates export' pulls down the latest template
3131
// and writes it to stdout.
3232
t.Run("Stdout", func(t *testing.T) {
3333
t.Parallel()
@@ -53,7 +53,7 @@ func TestTemplatePull(t *testing.T) {
5353
// are being sorted correctly.
5454
_ = coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, source2, template.ID)
5555

56-
cmd, root := clitest.New(t, "templates", "pull", template.Name)
56+
cmd, root := clitest.New(t, "templates", "export", template.Name)
5757
clitest.SetupConfig(t, client, root)
5858

5959
var buf bytes.Buffer
@@ -65,7 +65,7 @@ func TestTemplatePull(t *testing.T) {
6565
require.True(t, bytes.Equal(expected, buf.Bytes()), "tar files differ")
6666
})
6767

68-
// ToFile tests that 'templates pull' pulls down the latest template
68+
// ToFile tests that 'templates export' pulls down the latest template
6969
// and writes it to the correct directory.
7070
t.Run("ToFile", func(t *testing.T) {
7171
t.Parallel()
@@ -101,7 +101,7 @@ func TestTemplatePull(t *testing.T) {
101101
require.NoError(t, err)
102102
_ = fi.Close()
103103

104-
cmd, root := clitest.New(t, "templates", "pull", template.Name, dest)
104+
cmd, root := clitest.New(t, "templates", "export", template.Name, dest)
105105
clitest.SetupConfig(t, client, root)
106106

107107
pty := ptytest.New(t)

cli/templatepull.go

Lines changed: 49 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,81 @@
11
package cli
22

33
import (
4+
"archive/tar"
5+
"bytes"
46
"fmt"
5-
"io/fs"
7+
"io"
68
"os"
7-
"sort"
9+
"path/filepath"
810

911
"github.com/spf13/cobra"
1012
"golang.org/x/xerrors"
1113

1214
"github.com/coder/coder/cli/cliui"
13-
"github.com/coder/coder/codersdk"
1415
)
1516

16-
func fetchTemplateArchiveBytes(cmd *cobra.Command, templateName string) ([]byte, error) {
17-
ctx := cmd.Context()
18-
client, err := createClient(cmd)
19-
if err != nil {
20-
return nil, xerrors.Errorf("create client: %w", err)
21-
}
22-
23-
// TODO(JonA): Do we need to add a flag for organization?
24-
organization, err := currentOrganization(cmd, client)
25-
if err != nil {
26-
return nil, xerrors.Errorf("current organization: %w", err)
27-
}
28-
29-
template, err := client.TemplateByName(ctx, organization.ID, templateName)
30-
if err != nil {
31-
return nil, xerrors.Errorf("template by name: %w", err)
32-
}
33-
34-
// Pull the versions for the template. We'll find the latest
35-
// one and download the source.
36-
versions, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
37-
TemplateID: template.ID,
38-
})
39-
if err != nil {
40-
return nil, xerrors.Errorf("template versions by template: %w", err)
41-
}
42-
43-
if len(versions) == 0 {
44-
return nil, xerrors.Errorf("no template versions for template %q", templateName)
45-
}
46-
47-
// Sort the slice from newest to oldest template.
48-
sort.SliceStable(versions, func(i, j int) bool {
49-
return versions[i].CreatedAt.After(versions[j].CreatedAt)
50-
})
51-
52-
latest := versions[0]
17+
func TarBytesToTree(destination string, raw []byte) error {
18+
err := os.Mkdir(destination, 0700)
19+
20+
archiveReader := tar.NewReader(bytes.NewReader(raw))
21+
hdr, err := archiveReader.Next()
22+
for err != io.EOF {
23+
if hdr == nil { // some blog post indicated this could happen sometimes
24+
continue
25+
}
26+
filename := filepath.FromSlash(fmt.Sprintf("%s/%s", destination, hdr.Name))
27+
switch hdr.Typeflag {
28+
case tar.TypeDir:
29+
err = os.Mkdir(filename, 0700)
30+
if err != nil {
31+
return xerrors.Errorf("pulling template directory: %w", err)
32+
}
33+
case tar.TypeReg:
34+
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0600)
35+
if err != nil {
36+
return xerrors.Errorf("unable to create template file: %w", err)
37+
}
5338

54-
// Download the tar archive.
55-
raw, ctype, err := client.Download(ctx, latest.Job.StorageSource)
56-
if err != nil {
57-
return nil, xerrors.Errorf("download template: %w", err)
58-
}
39+
_, err = io.Copy(f, archiveReader)
40+
if err != nil {
41+
f.Close() // is this necessary?
42+
return xerrors.Errorf("error writing template file: %w", err)
43+
}
44+
f.Close()
45+
}
5946

60-
if ctype != codersdk.ContentTypeTar {
61-
return nil, xerrors.Errorf("unexpected Content-Type %q, expecting %q", ctype, codersdk.ContentTypeTar)
47+
hdr, err = archiveReader.Next()
6248
}
63-
return raw, nil
49+
return nil
6450
}
6551

6652
func templatePull() *cobra.Command {
6753
cmd := &cobra.Command{
68-
Use: "pull <name> [destination]",
69-
Short: "Download the latest version of a template to a path.",
54+
Use: "pull <template name> [destination]",
55+
Short: "Download the named template's contents into a subdirectory.",
56+
Long: "Download the named template's contents and extract them into a subdirectory named according to the destination or <template name> if no destination is specified.",
7057
Args: cobra.RangeArgs(1, 2),
7158
RunE: func(cmd *cobra.Command, args []string) error {
72-
raw, err := fetchTemplateArchiveBytes(cmd, args[0])
73-
if err != nil {
74-
return err
75-
}
76-
77-
var dest string
59+
templateName := args[0]
60+
var destination string
7861
if len(args) > 1 {
79-
dest = args[1]
62+
destination = args[1]
63+
} else {
64+
destination = templateName
8065
}
8166

82-
// If the destination is empty then we write to stdout
83-
// and bail early.
84-
if dest == "" {
85-
_, err = cmd.OutOrStdout().Write(raw)
86-
if err != nil {
87-
return xerrors.Errorf("write stdout: %w", err)
88-
}
89-
return nil
67+
raw, err := fetchTemplateArchiveBytes(cmd, templateName)
68+
if err != nil {
69+
return err
9070
}
9171

9272
// Stat the destination to ensure nothing exists already.
93-
fi, err := os.Stat(dest)
94-
if err != nil && !xerrors.Is(err, fs.ErrNotExist) {
95-
return xerrors.Errorf("stat destination: %w", err)
96-
}
97-
98-
if fi != nil && fi.IsDir() {
99-
// If the destination is a directory we just bail.
100-
return xerrors.Errorf("%q already exists.", dest)
101-
}
102-
103-
// If a file exists at the destination prompt the user
104-
// to ensure we don't overwrite something valuable.
105-
if fi != nil {
106-
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
107-
Text: fmt.Sprintf("%q already exists, do you want to overwrite it?", dest),
108-
IsConfirm: true,
109-
})
110-
if err != nil {
111-
return xerrors.Errorf("parse prompt: %w", err)
112-
}
113-
}
114-
115-
err = os.WriteFile(dest, raw, 0600)
116-
if err != nil {
117-
return xerrors.Errorf("write to path: %w", err)
73+
stat, err := os.Stat(destination)
74+
if stat != nil {
75+
return xerrors.Errorf("template file/directory already exists: %s", destination)
11876
}
11977

120-
return nil
78+
return TarBytesToTree(destination, raw)
12179
},
12280
}
12381

0 commit comments

Comments
 (0)