diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 57bcac8d2c131..8c5daa4fa4bbd 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -55,7 +55,7 @@ updates: - version-update:semver-major - package-ecosystem: "terraform" - directory: "/examples" + directory: "/examples/templates" schedule: interval: "weekly" time: "06:00" diff --git a/docs/about.md b/docs/about.md index f9ab0bfb59424..8f5e2f7ea770d 100644 --- a/docs/about.md +++ b/docs/about.md @@ -48,7 +48,7 @@ layer of infrastructure control. This additional layer allows admins to: - Enable persistent workspaces, which are like local machines, but faster and hosted by a cloud service -Coder includes [production-ready templates](../examples) for use with AWS EC2, +Coder includes [production-ready templates](../examples/templates) for use with AWS EC2, Azure, Google Cloud, Kubernetes, and more. ## What Coder is *not* diff --git a/docs/install.md b/docs/install.md index f2c08667b3935..04e680448f705 100644 --- a/docs/install.md +++ b/docs/install.md @@ -52,10 +52,13 @@ Coder](https://github.com/coder/coder/releases) installed. coder templates init ``` + Choose the "Develop in Docker" example to generate a sample template in the + `docker` subdirectory. + 1. Navigate into the new directory and create a new template: ```console - cd examples/docker + cd docker coder templates create ``` diff --git a/docs/templates.md b/docs/templates.md index 912dffc08baa0..063b185aa1911 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -9,7 +9,7 @@ to everybody. Users can also manually update their workspaces. ## Manage templates -Coder provides production-ready [sample templates](../examples/), but you can +Coder provides production-ready [sample templates](../examples/templates/), but you can modify the templates with Terraform. ```sh diff --git a/docs/workspaces.md b/docs/workspaces.md index 0dbdd0862fb1c..06be5e14c7c77 100644 --- a/docs/workspaces.md +++ b/docs/workspaces.md @@ -58,7 +58,7 @@ resources](./templates.md#persistent-and-ephemeral-resources). > ⚠️ To avoid data loss, refer to your template documentation for information on > where to store files, install software, etc., so that they persist. Default -> templates are documented in [../examples](../examples/). +> templates are documented in [../examples/templates](../examples/templates/). > > You can use `coder show ` to see which resources are > persistent and which are ephemeral. diff --git a/examples/examples.go b/examples/examples.go index a66ad9587f01d..79b5ab4424fd7 100644 --- a/examples/examples.go +++ b/examples/examples.go @@ -4,6 +4,8 @@ import ( "archive/tar" "bytes" "embed" + "io" + "io/fs" "path" "sync" @@ -13,8 +15,7 @@ import ( ) var ( - //go:embed */*.md - //go:embed */*.tf + //go:embed templates files embed.FS examples = make([]Example, 0) @@ -29,20 +30,30 @@ type Example struct { Markdown string `json:"markdown"` } +const rootDir = "templates" + // List returns all embedded examples. func List() ([]Example, error) { var returnError error parseExamples.Do(func() { - dirs, err := files.ReadDir(".") + files, err := fs.Sub(files, rootDir) + if err != nil { + returnError = xerrors.Errorf("get example fs: %w", err) + } + + dirs, err := fs.ReadDir(files, ".") if err != nil { returnError = xerrors.Errorf("read dir: %w", err) return } for _, dir := range dirs { + if !dir.IsDir() { + continue + } exampleID := dir.Name() // Each one of these is a example! - readme, err := files.ReadFile(path.Join(dir.Name(), "README.md")) + readme, err := fs.ReadFile(files, path.Join(dir.Name(), "README.md")) if err != nil { returnError = xerrors.Errorf("example %q does not contain README.md", exampleID) return @@ -110,54 +121,65 @@ func Archive(exampleID string) ([]byte, error) { return nil, xerrors.Errorf("example with id %q not found", exampleID) } - entries, err := files.ReadDir(exampleID) + exampleFiles, err := fs.Sub(files, path.Join(rootDir, exampleID)) if err != nil { - return nil, xerrors.Errorf("read dir: %w", err) + return nil, xerrors.Errorf("get example fs: %w", err) } var buffer bytes.Buffer tarWriter := tar.NewWriter(&buffer) - for _, entry := range entries { - file, err := files.Open(path.Join(exampleID, entry.Name())) - if err != nil { - return nil, xerrors.Errorf("open file: %w", err) - } - - info, err := file.Stat() + err = fs.WalkDir(exampleFiles, ".", func(path string, entry fs.DirEntry, err error) error { if err != nil { - return nil, xerrors.Errorf("stat file: %w", err) + return err } - if info.IsDir() { - continue - } - - data := make([]byte, info.Size()) - _, err = file.Read(data) + info, err := entry.Info() if err != nil { - return nil, xerrors.Errorf("read data: %w", err) + return xerrors.Errorf("stat file: %w", err) } header, err := tar.FileInfoHeader(info, entry.Name()) if err != nil { - return nil, xerrors.Errorf("get file header: %w", err) + return xerrors.Errorf("get file header: %w", err) } header.Mode = 0644 - err = tarWriter.WriteHeader(header) - if err != nil { - return nil, xerrors.Errorf("write file: %w", err) - } - - _, err = tarWriter.Write(data) - if err != nil { - return nil, xerrors.Errorf("write: %w", err) + if entry.IsDir() { + header.Name = path + "/" + + err = tarWriter.WriteHeader(header) + if err != nil { + return xerrors.Errorf("write file: %w", err) + } + } else { + header.Name = path + + file, err := exampleFiles.Open(path) + if err != nil { + return xerrors.Errorf("open file %s: %w", path, err) + } + defer file.Close() + + err = tarWriter.WriteHeader(header) + if err != nil { + return xerrors.Errorf("write file: %w", err) + } + + _, err = io.Copy(tarWriter, file) + if err != nil { + return xerrors.Errorf("write: %w", err) + } } + return nil + }) + if err != nil { + return nil, xerrors.Errorf("walk example directory: %w", err) } - err = tarWriter.Flush() + + err = tarWriter.Close() if err != nil { - return nil, xerrors.Errorf("flush archive: %w", err) + return nil, xerrors.Errorf("close archive: %w", err) } return buffer.Bytes(), nil diff --git a/examples/examples_test.go b/examples/examples_test.go index b7428dacadb82..5d4a8afa8c01e 100644 --- a/examples/examples_test.go +++ b/examples/examples_test.go @@ -1,6 +1,10 @@ package examples_test import ( + "archive/tar" + "bytes" + "errors" + "io" "testing" "github.com/stretchr/testify/require" @@ -17,3 +21,24 @@ func TestTemplate(t *testing.T) { _, err = examples.Archive(list[0].ID) require.NoError(t, err) } + +func TestSubdirs(t *testing.T) { + t.Parallel() + tarData, err := examples.Archive("docker-image-builds") + require.NoError(t, err) + + tarReader := tar.NewReader(bytes.NewReader(tarData)) + entryPaths := make(map[byte][]string) + for { + header, err := tarReader.Next() + if errors.Is(err, io.EOF) { + break + } + require.NoError(t, err) + + entryPaths[header.Typeflag] = append(entryPaths[header.Typeflag], header.Name) + } + + require.Subset(t, entryPaths[tar.TypeDir], []string{"./", "images/"}) + require.Subset(t, entryPaths[tar.TypeReg], []string{"README.md", "main.tf", "images/base.Dockerfile"}) +} diff --git a/examples/README.md b/examples/templates/README.md similarity index 91% rename from examples/README.md rename to examples/templates/README.md index d867f873b5c3e..a67232099adf0 100644 --- a/examples/README.md +++ b/examples/templates/README.md @@ -10,6 +10,6 @@ Clone this repository to create a project from any example listed here: ```sh git clone https://github.com/coder/coder -cd examples/aws-macos +cd examples/templates/aws-macos coder templates create ``` diff --git a/examples/aws-linux/README.md b/examples/templates/aws-linux/README.md similarity index 100% rename from examples/aws-linux/README.md rename to examples/templates/aws-linux/README.md diff --git a/examples/aws-linux/main.tf b/examples/templates/aws-linux/main.tf similarity index 100% rename from examples/aws-linux/main.tf rename to examples/templates/aws-linux/main.tf diff --git a/examples/aws-windows/README.md b/examples/templates/aws-windows/README.md similarity index 100% rename from examples/aws-windows/README.md rename to examples/templates/aws-windows/README.md diff --git a/examples/aws-windows/main.tf b/examples/templates/aws-windows/main.tf similarity index 100% rename from examples/aws-windows/main.tf rename to examples/templates/aws-windows/main.tf diff --git a/examples/docker-image-builds/README.md b/examples/templates/docker-image-builds/README.md similarity index 100% rename from examples/docker-image-builds/README.md rename to examples/templates/docker-image-builds/README.md diff --git a/examples/docker-image-builds/images/base.Dockerfile b/examples/templates/docker-image-builds/images/base.Dockerfile similarity index 100% rename from examples/docker-image-builds/images/base.Dockerfile rename to examples/templates/docker-image-builds/images/base.Dockerfile diff --git a/examples/docker-image-builds/images/java.Dockerfile b/examples/templates/docker-image-builds/images/java.Dockerfile similarity index 100% rename from examples/docker-image-builds/images/java.Dockerfile rename to examples/templates/docker-image-builds/images/java.Dockerfile diff --git a/examples/docker-image-builds/images/node.Dockerfile b/examples/templates/docker-image-builds/images/node.Dockerfile similarity index 100% rename from examples/docker-image-builds/images/node.Dockerfile rename to examples/templates/docker-image-builds/images/node.Dockerfile diff --git a/examples/docker-image-builds/main.tf b/examples/templates/docker-image-builds/main.tf similarity index 100% rename from examples/docker-image-builds/main.tf rename to examples/templates/docker-image-builds/main.tf diff --git a/examples/docker/README.md b/examples/templates/docker/README.md similarity index 100% rename from examples/docker/README.md rename to examples/templates/docker/README.md diff --git a/examples/docker/main.tf b/examples/templates/docker/main.tf similarity index 100% rename from examples/docker/main.tf rename to examples/templates/docker/main.tf diff --git a/examples/gcp-linux/README.md b/examples/templates/gcp-linux/README.md similarity index 100% rename from examples/gcp-linux/README.md rename to examples/templates/gcp-linux/README.md diff --git a/examples/gcp-linux/main.tf b/examples/templates/gcp-linux/main.tf similarity index 100% rename from examples/gcp-linux/main.tf rename to examples/templates/gcp-linux/main.tf diff --git a/examples/gcp-vm-container/README.md b/examples/templates/gcp-vm-container/README.md similarity index 100% rename from examples/gcp-vm-container/README.md rename to examples/templates/gcp-vm-container/README.md diff --git a/examples/gcp-vm-container/main.tf b/examples/templates/gcp-vm-container/main.tf similarity index 100% rename from examples/gcp-vm-container/main.tf rename to examples/templates/gcp-vm-container/main.tf diff --git a/examples/gcp-windows/README.md b/examples/templates/gcp-windows/README.md similarity index 100% rename from examples/gcp-windows/README.md rename to examples/templates/gcp-windows/README.md diff --git a/examples/gcp-windows/main.tf b/examples/templates/gcp-windows/main.tf similarity index 100% rename from examples/gcp-windows/main.tf rename to examples/templates/gcp-windows/main.tf diff --git a/examples/kubernetes-multi-service/README.md b/examples/templates/kubernetes-multi-service/README.md similarity index 100% rename from examples/kubernetes-multi-service/README.md rename to examples/templates/kubernetes-multi-service/README.md diff --git a/examples/kubernetes-multi-service/main.tf b/examples/templates/kubernetes-multi-service/main.tf similarity index 93% rename from examples/kubernetes-multi-service/main.tf rename to examples/templates/kubernetes-multi-service/main.tf index b720734d4c195..19eae429fcd3b 100644 --- a/examples/kubernetes-multi-service/main.tf +++ b/examples/templates/kubernetes-multi-service/main.tf @@ -41,7 +41,7 @@ variable "step3_certificate" { type = string sensitive = true description = <<-EOF - Use docs at https://github.com/coder/coder/tree/main/examples/kubernetes-multi-service#serviceaccount to create a ServiceAccount for Coder and grab values. + 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. Enter CA certificate @@ -53,7 +53,7 @@ variable "step4_token" { type = string sensitive = true description = <<-EOF - Enter token (refer to docs at https://github.com/coder/coder/tree/main/examples/kubernetes-multi-service#serviceaccount) + Enter token (refer to docs at https://github.com/coder/coder/tree/main/examples/templates/kubernetes-multi-service#serviceaccount) Leave blank if using ~/.kube/config (from step 1) EOF @@ -63,7 +63,7 @@ variable "step5_coder_namespace" { type = string sensitive = true description = <<-EOF - Enter namespace (refer to docs at https://github.com/coder/coder/tree/main/examples/kubernetes-multi-service#serviceaccount) + Enter namespace (refer to docs at https://github.com/coder/coder/tree/main/examples/templates/kubernetes-multi-service#serviceaccount) Leave blank if using ~/.kube/config (from step 1) EOF