Skip to content

Commit f8410de

Browse files
authored
fix: include subdirectories in example templates (coder#1715)
1 parent 5492ab7 commit f8410de

File tree

27 files changed

+91
-41
lines changed

27 files changed

+91
-41
lines changed

.github/dependabot.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ updates:
5555
- version-update:semver-major
5656

5757
- package-ecosystem: "terraform"
58-
directory: "/examples"
58+
directory: "/examples/templates"
5959
schedule:
6060
interval: "weekly"
6161
time: "06:00"

docs/about.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ layer of infrastructure control. This additional layer allows admins to:
4848
- Enable persistent workspaces, which are like local machines, but faster and
4949
hosted by a cloud service
5050

51-
Coder includes [production-ready templates](../examples) for use with AWS EC2,
51+
Coder includes [production-ready templates](../examples/templates) for use with AWS EC2,
5252
Azure, Google Cloud, Kubernetes, and more.
5353

5454
## What Coder is *not*

docs/install.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,13 @@ Coder](https://github.com/coder/coder/releases) installed.
5252
coder templates init
5353
```
5454

55+
Choose the "Develop in Docker" example to generate a sample template in the
56+
`docker` subdirectory.
57+
5558
1. Navigate into the new directory and create a new template:
5659

5760
```console
58-
cd examples/docker
61+
cd docker
5962
coder templates create
6063
```
6164

docs/templates.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ to everybody. Users can also manually update their workspaces.
99

1010
## Manage templates
1111

12-
Coder provides production-ready [sample templates](../examples/), but you can
12+
Coder provides production-ready [sample templates](../examples/templates/), but you can
1313
modify the templates with Terraform.
1414

1515
```sh

docs/workspaces.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ resources](./templates.md#persistent-and-ephemeral-resources).
5858

5959
> ⚠️ To avoid data loss, refer to your template documentation for information on
6060
> where to store files, install software, etc., so that they persist. Default
61-
> templates are documented in [../examples](../examples/).
61+
> templates are documented in [../examples/templates](../examples/templates/).
6262
>
6363
> You can use `coder show <workspace-name>` to see which resources are
6464
> persistent and which are ephemeral.

examples/examples.go

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"archive/tar"
55
"bytes"
66
"embed"
7+
"io"
8+
"io/fs"
79
"path"
810
"sync"
911

@@ -13,8 +15,7 @@ import (
1315
)
1416

1517
var (
16-
//go:embed */*.md
17-
//go:embed */*.tf
18+
//go:embed templates
1819
files embed.FS
1920

2021
examples = make([]Example, 0)
@@ -29,20 +30,30 @@ type Example struct {
2930
Markdown string `json:"markdown"`
3031
}
3132

33+
const rootDir = "templates"
34+
3235
// List returns all embedded examples.
3336
func List() ([]Example, error) {
3437
var returnError error
3538
parseExamples.Do(func() {
36-
dirs, err := files.ReadDir(".")
39+
files, err := fs.Sub(files, rootDir)
40+
if err != nil {
41+
returnError = xerrors.Errorf("get example fs: %w", err)
42+
}
43+
44+
dirs, err := fs.ReadDir(files, ".")
3745
if err != nil {
3846
returnError = xerrors.Errorf("read dir: %w", err)
3947
return
4048
}
4149

4250
for _, dir := range dirs {
51+
if !dir.IsDir() {
52+
continue
53+
}
4354
exampleID := dir.Name()
4455
// Each one of these is a example!
45-
readme, err := files.ReadFile(path.Join(dir.Name(), "README.md"))
56+
readme, err := fs.ReadFile(files, path.Join(dir.Name(), "README.md"))
4657
if err != nil {
4758
returnError = xerrors.Errorf("example %q does not contain README.md", exampleID)
4859
return
@@ -110,54 +121,65 @@ func Archive(exampleID string) ([]byte, error) {
110121
return nil, xerrors.Errorf("example with id %q not found", exampleID)
111122
}
112123

113-
entries, err := files.ReadDir(exampleID)
124+
exampleFiles, err := fs.Sub(files, path.Join(rootDir, exampleID))
114125
if err != nil {
115-
return nil, xerrors.Errorf("read dir: %w", err)
126+
return nil, xerrors.Errorf("get example fs: %w", err)
116127
}
117128

118129
var buffer bytes.Buffer
119130
tarWriter := tar.NewWriter(&buffer)
120131

121-
for _, entry := range entries {
122-
file, err := files.Open(path.Join(exampleID, entry.Name()))
123-
if err != nil {
124-
return nil, xerrors.Errorf("open file: %w", err)
125-
}
126-
127-
info, err := file.Stat()
132+
err = fs.WalkDir(exampleFiles, ".", func(path string, entry fs.DirEntry, err error) error {
128133
if err != nil {
129-
return nil, xerrors.Errorf("stat file: %w", err)
134+
return err
130135
}
131136

132-
if info.IsDir() {
133-
continue
134-
}
135-
136-
data := make([]byte, info.Size())
137-
_, err = file.Read(data)
137+
info, err := entry.Info()
138138
if err != nil {
139-
return nil, xerrors.Errorf("read data: %w", err)
139+
return xerrors.Errorf("stat file: %w", err)
140140
}
141141

142142
header, err := tar.FileInfoHeader(info, entry.Name())
143143
if err != nil {
144-
return nil, xerrors.Errorf("get file header: %w", err)
144+
return xerrors.Errorf("get file header: %w", err)
145145
}
146146
header.Mode = 0644
147147

148-
err = tarWriter.WriteHeader(header)
149-
if err != nil {
150-
return nil, xerrors.Errorf("write file: %w", err)
151-
}
152-
153-
_, err = tarWriter.Write(data)
154-
if err != nil {
155-
return nil, xerrors.Errorf("write: %w", err)
148+
if entry.IsDir() {
149+
header.Name = path + "/"
150+
151+
err = tarWriter.WriteHeader(header)
152+
if err != nil {
153+
return xerrors.Errorf("write file: %w", err)
154+
}
155+
} else {
156+
header.Name = path
157+
158+
file, err := exampleFiles.Open(path)
159+
if err != nil {
160+
return xerrors.Errorf("open file %s: %w", path, err)
161+
}
162+
defer file.Close()
163+
164+
err = tarWriter.WriteHeader(header)
165+
if err != nil {
166+
return xerrors.Errorf("write file: %w", err)
167+
}
168+
169+
_, err = io.Copy(tarWriter, file)
170+
if err != nil {
171+
return xerrors.Errorf("write: %w", err)
172+
}
156173
}
174+
return nil
175+
})
176+
if err != nil {
177+
return nil, xerrors.Errorf("walk example directory: %w", err)
157178
}
158-
err = tarWriter.Flush()
179+
180+
err = tarWriter.Close()
159181
if err != nil {
160-
return nil, xerrors.Errorf("flush archive: %w", err)
182+
return nil, xerrors.Errorf("close archive: %w", err)
161183
}
162184

163185
return buffer.Bytes(), nil

examples/examples_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package examples_test
22

33
import (
4+
"archive/tar"
5+
"bytes"
6+
"errors"
7+
"io"
48
"testing"
59

610
"github.com/stretchr/testify/require"
@@ -17,3 +21,24 @@ func TestTemplate(t *testing.T) {
1721
_, err = examples.Archive(list[0].ID)
1822
require.NoError(t, err)
1923
}
24+
25+
func TestSubdirs(t *testing.T) {
26+
t.Parallel()
27+
tarData, err := examples.Archive("docker-image-builds")
28+
require.NoError(t, err)
29+
30+
tarReader := tar.NewReader(bytes.NewReader(tarData))
31+
entryPaths := make(map[byte][]string)
32+
for {
33+
header, err := tarReader.Next()
34+
if errors.Is(err, io.EOF) {
35+
break
36+
}
37+
require.NoError(t, err)
38+
39+
entryPaths[header.Typeflag] = append(entryPaths[header.Typeflag], header.Name)
40+
}
41+
42+
require.Subset(t, entryPaths[tar.TypeDir], []string{"./", "images/"})
43+
require.Subset(t, entryPaths[tar.TypeReg], []string{"README.md", "main.tf", "images/base.Dockerfile"})
44+
}

examples/README.md renamed to examples/templates/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ Clone this repository to create a project from any example listed here:
1010

1111
```sh
1212
git clone https://github.com/coder/coder
13-
cd examples/aws-macos
13+
cd examples/templates/aws-macos
1414
coder templates create
1515
```
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

examples/kubernetes-multi-service/main.tf renamed to examples/templates/kubernetes-multi-service/main.tf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ variable "step3_certificate" {
4141
type = string
4242
sensitive = true
4343
description = <<-EOF
44-
Use docs at https://github.com/coder/coder/tree/main/examples/kubernetes-multi-service#serviceaccount to create a ServiceAccount for Coder and grab values.
44+
Use docs at https://github.com/coder/coder/tree/main/examples/templates/kubernetes-multi-service#serviceaccount to create a ServiceAccount for Coder and grab values.
4545
4646
Enter CA certificate
4747
@@ -53,7 +53,7 @@ variable "step4_token" {
5353
type = string
5454
sensitive = true
5555
description = <<-EOF
56-
Enter token (refer to docs at https://github.com/coder/coder/tree/main/examples/kubernetes-multi-service#serviceaccount)
56+
Enter token (refer to docs at https://github.com/coder/coder/tree/main/examples/templates/kubernetes-multi-service#serviceaccount)
5757
5858
Leave blank if using ~/.kube/config (from step 1)
5959
EOF
@@ -63,7 +63,7 @@ variable "step5_coder_namespace" {
6363
type = string
6464
sensitive = true
6565
description = <<-EOF
66-
Enter namespace (refer to docs at https://github.com/coder/coder/tree/main/examples/kubernetes-multi-service#serviceaccount)
66+
Enter namespace (refer to docs at https://github.com/coder/coder/tree/main/examples/templates/kubernetes-multi-service#serviceaccount)
6767
6868
Leave blank if using ~/.kube/config (from step 1)
6969
EOF

0 commit comments

Comments
 (0)