Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit390217b

Browse files
feat(site): add create template from scratch (#12082)
1 parent2b307c7 commit390217b

18 files changed

+317
-34
lines changed

‎cli/testdata/coder_templates_init_--help.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ USAGE:
66
Get started with a templated template.
77

88
OPTIONS:
9-
--id aws-devcontainer|aws-linux|aws-windows|azure-linux|do-linux|docker|gcp-devcontainer|gcp-linux|gcp-vm-container|gcp-windows|kubernetes|nomad-docker
9+
--id aws-devcontainer|aws-linux|aws-windows|azure-linux|do-linux|docker|gcp-devcontainer|gcp-linux|gcp-vm-container|gcp-windows|kubernetes|nomad-docker|scratch
1010
Specify a given example template by ID.
1111

1212
———

‎docs/cli/templates_init.md

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎examples/examples.gen.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,5 +156,14 @@
156156
"container"
157157
],
158158
"markdown": "\n# Remote Development on Nomad\n\nProvision Nomad Jobs as [Coder workspaces](https://coder.com/docs/v2/latest/workspaces) with this example template. This example shows how to use Nomad service tasks to be used as a development environment using docker and host csi volumes.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## Prerequisites\n\n- [Nomad](https://www.nomadproject.io/downloads)\n- [Docker](https://docs.docker.com/get-docker/)\n\n## Setup\n\n### 1. Start the CSI Host Volume Plugin\n\nThe CSI Host Volume plugin is used to mount host volumes into Nomad tasks. This is useful for development environments where you want to mount persistent volumes into your container workspace.\n\n1. Login to the Nomad server using SSH.\n\n2. Append the following stanza to your Nomad server configuration file and restart the nomad service.\n\n ```hcl\n plugin \"docker\" {\n config {\n allow_privileged = true\n }\n }\n ```\n\n ```shell\n sudo systemctl restart nomad\n ```\n\n3. Create a file `hostpath.nomad` with following content:\n\n ```hcl\n job \"hostpath-csi-plugin\" {\n datacenters = [\"dc1\"]\n type = \"system\"\n\n group \"csi\" {\n task \"plugin\" {\n driver = \"docker\"\n\n config {\n image = \"registry.k8s.io/sig-storage/hostpathplugin:v1.10.0\"\n\n args = [\n \"--drivername=csi-hostpath\",\n \"--v=5\",\n \"--endpoint=${CSI_ENDPOINT}\",\n \"--nodeid=node-${NOMAD_ALLOC_INDEX}\",\n ]\n\n privileged = true\n }\n\n csi_plugin {\n id = \"hostpath\"\n type = \"monolith\"\n mount_dir = \"/csi\"\n }\n\n resources {\n cpu = 256\n memory = 128\n }\n }\n }\n }\n ```\n\n4. Run the job:\n\n ```shell\n nomad job run hostpath.nomad\n ```\n\n### 2. Setup the Nomad Template\n\n1. Create the template by running the following command:\n\n ```shell\n coder template init nomad-docker\n cd nomad-docker\n coder template push\n ```\n\n2. Set up Nomad server address and optional authentication:\n\n3. Create a new workspace and start developing.\n"
159+
},
160+
{
161+
"id":"scratch",
162+
"url":"",
163+
"name":"Scratch",
164+
"description":"A minimal starter template for Coder",
165+
"icon":"/emojis/1f4e6.png",
166+
"tags": [],
167+
"markdown":"\n# A minimal Scaffolding for a Coder Template\n\nUse this starter template as a basis to create your own unique template from scratch.\n"
159168
}
160169
]

‎examples/examples.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ var (
3535
//go:embed templates/gcp-windows
3636
//go:embed templates/kubernetes
3737
//go:embed templates/nomad-docker
38+
//go:embed templates/scratch
3839
files embed.FS
3940

4041
exampleBasePath="https://github.com/coder/coder/tree/main/examples/templates/"

‎examples/templates/scratch/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
display_name:Scratch
3+
description:A minimal starter template for Coder
4+
icon:../../../site/static/emojis/1f4e6.png
5+
maintainer_github:coder
6+
verified:true
7+
tags:[]
8+
---
9+
10+
#A minimal Scaffolding for a Coder Template
11+
12+
Use this starter template as a basis to create your own unique template from scratch.

‎examples/templates/scratch/main.tf

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
terraform {
2+
required_providers {
3+
coder={
4+
source="coder/coder"
5+
}
6+
}
7+
}
8+
9+
data"coder_provisioner""me" {}
10+
11+
data"coder_workspace""me" {}
12+
13+
resource"coder_agent""main" {
14+
arch=data.coder_provisioner.me.arch
15+
os=data.coder_provisioner.me.os
16+
17+
metadata {
18+
display_name="CPU Usage"
19+
key="0_cpu_usage"
20+
script="coder stat cpu"
21+
interval=10
22+
timeout=1
23+
}
24+
25+
metadata {
26+
display_name="RAM Usage"
27+
key="1_ram_usage"
28+
script="coder stat mem"
29+
interval=10
30+
timeout=1
31+
}
32+
}
33+
34+
# Use this to set environment variables in your workspace
35+
# details: https://registry.terraform.io/providers/coder/coder/latest/docs/resources/env
36+
resource"coder_env""welcome_message" {
37+
agent_id=coder_agent.main.id
38+
name="WELCOME_MESSAGE"
39+
value="Welcome to your Coder workspace!"
40+
}
41+
42+
# Adds code-server
43+
# See all available modules at https://registry.coder.com
44+
module"code-server" {
45+
source="registry.coder.com/modules/code-server/coder"
46+
version="1.0.2"
47+
agent_id=coder_agent.main.id
48+
}
49+
50+
# Runs a script at workspace start/stop or on a cron schedule
51+
# details: https://registry.terraform.io/providers/coder/coder/latest/docs/resources/script
52+
resource"coder_script""startup_script" {
53+
agent_id=coder_agent.main.id
54+
display_name="Startup Script"
55+
script=<<-EOF
56+
#!/bin/sh
57+
set -e
58+
# Run programs at workspace startup
59+
EOF
60+
run_on_start=true
61+
start_blocks_login=true
62+
}

‎site/e2e/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export const createTemplate = async (
134134
constname=randomName();
135135
awaitpage.getByLabel("Name *").fill(name);
136136
awaitpage.getByTestId("form-submit").click();
137-
awaitexpect(page).toHaveURL("/templates/"+name,{
137+
awaitexpect(page).toHaveURL(`/templates/${name}/files`,{
138138
timeout:30000,
139139
});
140140
returnname;

‎site/src/pages/CreateTemplatePage/CreateTemplatePage.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ test("Create template from starter template", async () => {
8888
);
8989
awaitwaitFor(()=>expect(API.createTemplate).toBeCalledTimes(1));
9090
expect(router.state.location.pathname).toEqual(
91-
`/templates/${MockTemplate.name}`,
91+
`/templates/${MockTemplate.name}/files`,
9292
);
9393
expect(API.createTemplateVersion).toHaveBeenCalledWith(MockOrganization.id,{
9494
example_id:"aws-windows",
@@ -138,7 +138,7 @@ test("Create template from duplicating a template", async () => {
138138
);
139139
awaitwaitFor(()=>{
140140
expect(router.state.location.pathname).toEqual(
141-
`/templates/${MockTemplate.name}`,
141+
`/templates/${MockTemplate.name}/files`,
142142
);
143143
});
144144
});

‎site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@ import { FullPageHorizontalForm } from "components/FullPageForm/FullPageHorizont
66
import{DuplicateTemplateView}from"./DuplicateTemplateView";
77
import{ImportStarterTemplateView}from"./ImportStarterTemplateView";
88
import{UploadTemplateView}from"./UploadTemplateView";
9+
import{Template}from"api/typesGenerated";
910

1011
constCreateTemplatePage:FC=()=>{
1112
constnavigate=useNavigate();
1213
const[searchParams]=useSearchParams();
1314

15+
constonSuccess=(template:Template)=>{
16+
navigate(`/templates/${template.name}/files`);
17+
};
18+
1419
constonCancel=()=>{
1520
navigate(-1);
1621
};
@@ -23,11 +28,11 @@ const CreateTemplatePage: FC = () => {
2328

2429
<FullPageHorizontalFormtitle="Create Template"onCancel={onCancel}>
2530
{searchParams.has("fromTemplate") ?(
26-
<DuplicateTemplateView/>
31+
<DuplicateTemplateViewonSuccess={onSuccess}/>
2732
) :searchParams.has("exampleId") ?(
28-
<ImportStarterTemplateView/>
33+
<ImportStarterTemplateViewonSuccess={onSuccess}/>
2934
) :(
30-
<UploadTemplateView/>
35+
<UploadTemplateViewonSuccess={onSuccess}/>
3136
)}
3237
</FullPageHorizontalForm>
3338
</>

‎site/src/pages/CreateTemplatePage/DuplicateTemplateView.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,15 @@ import { ErrorAlert } from "components/Alert/ErrorAlert";
1515
import{Loader}from"components/Loader/Loader";
1616
import{CreateTemplateForm}from"./CreateTemplateForm";
1717
import{firstVersionFromFile,getFormPermissions,newTemplate}from"./utils";
18+
import{Template}from"api/typesGenerated";
1819

19-
exportconstDuplicateTemplateView:FC=()=>{
20+
typeDuplicateTemplateViewProps={
21+
onSuccess:(template:Template)=>void;
22+
};
23+
24+
exportconstDuplicateTemplateView:FC<DuplicateTemplateViewProps>=({
25+
onSuccess,
26+
})=>{
2027
constnavigate=useNavigate();
2128
constorganizationId=useOrganizationId();
2229
const[searchParams]=useSearchParams();
@@ -79,7 +86,7 @@ export const DuplicateTemplateView: FC = () => {
7986
),
8087
template:newTemplate(formData),
8188
});
82-
navigate(`/templates/${template.name}`);
89+
onSuccess(template);
8390
}}
8491
/>
8592
);

‎site/src/pages/CreateTemplatePage/ImportStarterTemplateView.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,15 @@ import {
1818
getFormPermissions,
1919
newTemplate,
2020
}from"./utils";
21+
import{Template}from"api/typesGenerated";
2122

22-
exportconstImportStarterTemplateView:FC=()=>{
23+
typeImportStarterTemplateViewProps={
24+
onSuccess:(template:Template)=>void;
25+
};
26+
27+
exportconstImportStarterTemplateView:FC<ImportStarterTemplateViewProps>=({
28+
onSuccess,
29+
})=>{
2330
constnavigate=useNavigate();
2431
constorganizationId=useOrganizationId();
2532
const[searchParams]=useSearchParams();
@@ -76,7 +83,7 @@ export const ImportStarterTemplateView: FC = () => {
7683
),
7784
template:newTemplate(formData),
7885
});
79-
navigate(`/templates/${template.name}`);
86+
onSuccess(template);
8087
}}
8188
/>
8289
);

‎site/src/pages/CreateTemplatePage/UploadTemplateView.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,16 @@ import { useOrganizationId } from "contexts/auth/useOrganizationId";
1111
import{useDashboard}from"modules/dashboard/useDashboard";
1212
import{CreateTemplateForm}from"./CreateTemplateForm";
1313
import{firstVersionFromFile,getFormPermissions,newTemplate}from"./utils";
14+
import{Template}from"api/typesGenerated";
15+
import{FC}from"react";
1416

15-
exportconstUploadTemplateView=()=>{
17+
typeUploadTemplateViewProps={
18+
onSuccess:(template:Template)=>void;
19+
};
20+
21+
exportconstUploadTemplateView:FC<UploadTemplateViewProps>=({
22+
onSuccess,
23+
})=>{
1624
constnavigate=useNavigate();
1725
constorganizationId=useOrganizationId();
1826

@@ -61,7 +69,7 @@ export const UploadTemplateView = () => {
6169
),
6270
template:newTemplate(formData),
6371
});
64-
navigate(`/templates/${template.name}`);
72+
onSuccess(template);
6573
}}
6674
/>
6775
);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import{render,screen}from"@testing-library/react";
2+
importStarterTemplatesPagefrom"./StarterTemplatesPage";
3+
import{AppProviders}from"App";
4+
import{RouterProvider,createMemoryRouter}from"react-router-dom";
5+
import{RequireAuth}from"contexts/auth/RequireAuth";
6+
import{rest}from"msw";
7+
import{
8+
MockTemplateExample,
9+
MockTemplateExample2,
10+
}from"testHelpers/entities";
11+
import{server}from"testHelpers/server";
12+
13+
test("does not display the scratch template",async()=>{
14+
server.use(
15+
rest.get(
16+
"api/v2/organizations/:organizationId/templates/examples",
17+
(req,res,ctx)=>{
18+
returnres(
19+
ctx.status(200),
20+
ctx.json([
21+
MockTemplateExample,
22+
MockTemplateExample2,
23+
{
24+
...MockTemplateExample,
25+
id:"scratch",
26+
name:"Scratch",
27+
description:"Create a template from scratch",
28+
},
29+
]),
30+
);
31+
},
32+
),
33+
);
34+
35+
render(
36+
<AppProviders>
37+
<RouterProvider
38+
router={createMemoryRouter(
39+
[
40+
{
41+
element:<RequireAuth/>,
42+
children:[
43+
{
44+
path:"/starter-templates",
45+
element:<StarterTemplatesPage/>,
46+
},
47+
],
48+
},
49+
],
50+
{initialEntries:["/starter-templates"]},
51+
)}
52+
/>
53+
</AppProviders>,
54+
);
55+
56+
awaitscreen.findByText(MockTemplateExample.name);
57+
screen.getByText(MockTemplateExample2.name);
58+
expect(screen.queryByText("Scratch")).not.toBeInTheDocument();
59+
});

‎site/src/pages/StarterTemplatesPage/StarterTemplatesPage.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import { useOrganizationId } from "contexts/auth/useOrganizationId";
66
import{pageTitle}from"utils/page";
77
import{getTemplatesByTag}from"utils/starterTemplates";
88
import{StarterTemplatesPageView}from"./StarterTemplatesPageView";
9+
import{TemplateExample}from"api/typesGenerated";
910

1011
constStarterTemplatesPage:FC=()=>{
1112
constorganizationId=useOrganizationId();
1213
consttemplateExamplesQuery=useQuery(templateExamples(organizationId));
1314
conststarterTemplatesByTag=templateExamplesQuery.data
14-
?getTemplatesByTag(templateExamplesQuery.data)
15+
?// Currently, the scratch template should not be displayed on the starter templates page.
16+
getTemplatesByTag(removeScratchExample(templateExamplesQuery.data))
1517
:undefined;
1618

1719
return(
@@ -28,4 +30,8 @@ const StarterTemplatesPage: FC = () => {
2830
);
2931
};
3032

33+
constremoveScratchExample=(data:TemplateExample[])=>{
34+
returndata.filter((example)=>example.id!=="scratch");
35+
};
36+
3137
exportdefaultStarterTemplatesPage;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import{CreateTemplateButton}from"./CreateTemplateButton";
2+
importtype{Meta,StoryObj}from"@storybook/react";
3+
import{userEvent,screen}from"@storybook/test";
4+
5+
constmeta:Meta<typeofCreateTemplateButton>={
6+
title:"pages/TemplatesPage/CreateTemplateButton",
7+
component:CreateTemplateButton,
8+
};
9+
10+
exportdefaultmeta;
11+
typeStory=StoryObj<typeofCreateTemplateButton>;
12+
13+
exportconstClose:Story={};
14+
15+
exportconstOpen:Story={
16+
play:async({ step})=>{
17+
constuser=userEvent.setup();
18+
awaitstep("click on trigger",async()=>{
19+
awaituser.click(screen.getByRole("button"));
20+
});
21+
},
22+
};

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp