chore: Rename Projects to Templates (#880)

Customer feedback indicated projects was a confusing name.
After querying the team internally, it seemed unanimous
that it is indeed a confusing name.

Here's for a lil less confusion @ashmeer7 🥂
This commit is contained in:
Kyle Carberry 2022-04-06 12:42:40 -05:00 committed by GitHub
parent 584c8b4fc3
commit 02ad3f14f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
109 changed files with 2548 additions and 2547 deletions

View File

@ -36,9 +36,9 @@ func SetupConfig(t *testing.T, client *codersdk.Client, root config.Root) {
require.NoError(t, err)
}
// CreateProjectVersionSource writes the echo provisioner responses into a
// CreateTemplateVersionSource writes the echo provisioner responses into a
// new temporary testing directory.
func CreateProjectVersionSource(t *testing.T, responses *echo.Responses) string {
func CreateTemplateVersionSource(t *testing.T, responses *echo.Responses) string {
directory := t.TempDir()
data, err := echo.Tar(responses)
require.NoError(t, err)

View File

@ -17,7 +17,7 @@ func TestMain(m *testing.M) {
func TestCli(t *testing.T) {
t.Parallel()
clitest.CreateProjectVersionSource(t, nil)
clitest.CreateTemplateVersionSource(t, nil)
client := coderdtest.New(t, nil)
cmd, config := clitest.New(t)
clitest.SetupConfig(t, client, config)

View File

@ -10,7 +10,7 @@ import (
"github.com/coder/coder/codersdk"
)
func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.ProjectVersionParameterSchema) (string, error) {
func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.TemplateVersionParameterSchema) (string, error) {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), Styles.Bold.Render("var."+parameterSchema.Name))
if parameterSchema.Description != "" {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+strings.TrimSpace(strings.Join(strings.Split(parameterSchema.Description, "\n"), "\n "))+"\n")

View File

@ -19,10 +19,10 @@ func TestConfigSSH(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
tempFile, err := os.CreateTemp(t.TempDir(), "")
require.NoError(t, err)

View File

@ -150,7 +150,7 @@ func login() *cobra.Command {
cliui.Styles.Paragraph.Render(fmt.Sprintf("Welcome to Coder, %s! You're authenticated.", cliui.Styles.Keyword.Render(username)))+"\n")
_, _ = fmt.Fprintf(cmd.OutOrStdout(),
cliui.Styles.Paragraph.Render("Get started by creating a project: "+cliui.Styles.Code.Render("coder projects create"))+"\n")
cliui.Styles.Paragraph.Render("Get started by creating a template: "+cliui.Styles.Code.Render("coder templates create"))+"\n")
return nil
}

View File

@ -39,12 +39,12 @@ func parseScopeAndID(ctx context.Context, client *codersdk.Client, organization
}
scopeID = org.ID
}
case codersdk.ParameterProject:
project, err := client.ProjectByName(ctx, organization.ID, name)
case codersdk.ParameterTemplate:
template, err := client.TemplateByName(ctx, organization.ID, name)
if err != nil {
return scope, uuid.Nil, err
}
scopeID = project.ID
scopeID = template.ID
case codersdk.ParameterUser:
uid, _ := uuid.Parse(name)
user, err := client.User(ctx, uid)
@ -67,8 +67,8 @@ func parseParameterScope(scope string) (codersdk.ParameterScope, error) {
switch scope {
case string(codersdk.ParameterOrganization):
return codersdk.ParameterOrganization, nil
case string(codersdk.ParameterProject):
return codersdk.ParameterProject, nil
case string(codersdk.ParameterTemplate):
return codersdk.ParameterTemplate, nil
case string(codersdk.ParameterUser):
return codersdk.ParameterUser, nil
case string(codersdk.ParameterWorkspace):

View File

@ -43,9 +43,9 @@ func Root() *cobra.Command {
Example: cliui.Styles.Paragraph.Render(`Start Coder in "dev" mode. This dev-mode requires no further setup, and your local `+cliui.Styles.Code.Render("coder")+` CLI will be authenticated to talk to it. This makes it easy to experiment with Coder.`) + `
` + cliui.Styles.Code.Render("$ coder start --dev") + `
` + cliui.Styles.Paragraph.Render("Get started by creating a project from an example.") + `
` + cliui.Styles.Paragraph.Render("Get started by creating a template from an example.") + `
` + cliui.Styles.Code.Render("$ coder projects init"),
` + cliui.Styles.Code.Render("$ coder templates init"),
}
// Customizes the color of headings to make subcommands
// more visually appealing.
@ -65,7 +65,7 @@ func Root() *cobra.Command {
start(),
login(),
parameters(),
projects(),
templates(),
users(),
workspaces(),
ssh(),

View File

@ -31,7 +31,7 @@ func TestSSH(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
agentToken := uuid.NewString()
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionDryRun: echo.ProvisionComplete,
Provision: []*proto.Provision_Response{{
@ -51,9 +51,9 @@ func TestSSH(t *testing.T) {
},
}},
})
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
go func() {
// Run this async so the SSH command has to wait for
// the build and agent to connect!
@ -89,7 +89,7 @@ func TestSSH(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
agentToken := uuid.NewString()
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionDryRun: echo.ProvisionComplete,
Provision: []*proto.Provision_Response{{
@ -109,9 +109,9 @@ func TestSSH(t *testing.T) {
},
}},
})
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
go func() {
// Run this async so the SSH command has to wait for
// the build and agent to connect!

View File

@ -217,7 +217,7 @@ func start() *cobra.Command {
cliui.Styles.Field.Render("dev")+` mode. All data is in-memory! Do not use in production. Press `+cliui.Styles.Field.Render("ctrl+c")+` to clean up provisioned infrastructure.`))+
`
`+
cliui.Styles.Paragraph.Render(cliui.Styles.Wrap.Render(cliui.Styles.Prompt.String()+`Run `+cliui.Styles.Code.Render("coder projects init")+" in a new terminal to get started.\n"))+`
cliui.Styles.Paragraph.Render(cliui.Styles.Wrap.Render(cliui.Styles.Prompt.String()+`Run `+cliui.Styles.Code.Render("coder templates init")+" in a new terminal to get started.\n"))+`
`)
} else {
// This is helpful for tests, but can be silently ignored.

View File

@ -187,10 +187,10 @@ func TestStart(t *testing.T) {
coderdtest.NewProvisionerDaemon(t, client)
// Create a workspace so the cleanup occurs!
version := coderdtest.CreateProjectVersion(t, client, orgs[0].ID, nil)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, orgs[0].ID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, orgs[0].ID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, orgs[0].ID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
require.NoError(t, err)

View File

@ -20,7 +20,7 @@ import (
"github.com/coder/coder/provisionersdk"
)
func projectCreate() *cobra.Command {
func templateCreate() *cobra.Command {
var (
yes bool
directory string
@ -28,7 +28,7 @@ func projectCreate() *cobra.Command {
)
cmd := &cobra.Command{
Use: "create [name]",
Short: "Create a project from the current directory",
Short: "Create a template from the current directory",
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
if err != nil {
@ -39,15 +39,15 @@ func projectCreate() *cobra.Command {
return err
}
var projectName string
var templateName string
if len(args) == 0 {
projectName = filepath.Base(directory)
templateName = filepath.Base(directory)
} else {
projectName = args[0]
templateName = args[0]
}
_, err = client.ProjectByName(cmd.Context(), organization.ID, projectName)
_, err = client.TemplateByName(cmd.Context(), organization.ID, templateName)
if err == nil {
return xerrors.Errorf("A project already exists named %q!", projectName)
return xerrors.Errorf("A template already exists named %q!", templateName)
}
spin := spinner.New(spinner.CharSets[5], 100*time.Millisecond)
@ -69,14 +69,14 @@ func projectCreate() *cobra.Command {
spin = spinner.New(spinner.CharSets[5], 100*time.Millisecond)
spin.Writer = cmd.OutOrStdout()
spin.Suffix = cliui.Styles.Keyword.Render("Something")
job, parameters, err := createValidProjectVersion(cmd, client, organization, database.ProvisionerType(provisioner), resp.Hash)
job, parameters, err := createValidTemplateVersion(cmd, client, organization, database.ProvisionerType(provisioner), resp.Hash)
if err != nil {
return err
}
if !yes {
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Create project?",
Text: "Create template?",
IsConfirm: true,
Default: "yes",
})
@ -88,8 +88,8 @@ func projectCreate() *cobra.Command {
}
}
_, err = client.CreateProject(cmd.Context(), organization.ID, codersdk.CreateProjectRequest{
Name: projectName,
_, err = client.CreateTemplate(cmd.Context(), organization.ID, codersdk.CreateTemplateRequest{
Name: templateName,
VersionID: job.ID,
ParameterValues: parameters,
})
@ -97,7 +97,7 @@ func projectCreate() *cobra.Command {
return err
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "The %s project has been created!\n", projectName)
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "The %s template has been created!\n", templateName)
return nil
},
}
@ -113,9 +113,9 @@ func projectCreate() *cobra.Command {
return cmd
}
func createValidProjectVersion(cmd *cobra.Command, client *codersdk.Client, organization codersdk.Organization, provisioner database.ProvisionerType, hash string, parameters ...codersdk.CreateParameterRequest) (*codersdk.ProjectVersion, []codersdk.CreateParameterRequest, error) {
func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, organization codersdk.Organization, provisioner database.ProvisionerType, hash string, parameters ...codersdk.CreateParameterRequest) (*codersdk.TemplateVersion, []codersdk.CreateParameterRequest, error) {
before := time.Now()
version, err := client.CreateProjectVersion(cmd.Context(), organization.ID, codersdk.CreateProjectVersionRequest{
version, err := client.CreateTemplateVersion(cmd.Context(), organization.ID, codersdk.CreateTemplateVersionRequest{
StorageMethod: database.ProvisionerStorageMethodFile,
StorageSource: hash,
Provisioner: provisioner,
@ -127,14 +127,14 @@ func createValidProjectVersion(cmd *cobra.Command, client *codersdk.Client, orga
err = cliui.ProvisionerJob(cmd.Context(), cmd.OutOrStdout(), cliui.ProvisionerJobOptions{
Fetch: func() (codersdk.ProvisionerJob, error) {
version, err := client.ProjectVersion(cmd.Context(), version.ID)
version, err := client.TemplateVersion(cmd.Context(), version.ID)
return version.Job, err
},
Cancel: func() error {
return client.CancelProjectVersion(cmd.Context(), version.ID)
return client.CancelTemplateVersion(cmd.Context(), version.ID)
},
Logs: func() (<-chan codersdk.ProvisionerJobLog, error) {
return client.ProjectVersionLogsAfter(cmd.Context(), version.ID, before)
return client.TemplateVersionLogsAfter(cmd.Context(), version.ID, before)
},
})
if err != nil {
@ -142,28 +142,28 @@ func createValidProjectVersion(cmd *cobra.Command, client *codersdk.Client, orga
return nil, nil, err
}
}
version, err = client.ProjectVersion(cmd.Context(), version.ID)
version, err = client.TemplateVersion(cmd.Context(), version.ID)
if err != nil {
return nil, nil, err
}
parameterSchemas, err := client.ProjectVersionSchema(cmd.Context(), version.ID)
parameterSchemas, err := client.TemplateVersionSchema(cmd.Context(), version.ID)
if err != nil {
return nil, nil, err
}
parameterValues, err := client.ProjectVersionParameters(cmd.Context(), version.ID)
parameterValues, err := client.TemplateVersionParameters(cmd.Context(), version.ID)
if err != nil {
return nil, nil, err
}
if provisionerd.IsMissingParameterError(version.Job.Error) {
valuesBySchemaID := map[string]codersdk.ProjectVersionParameter{}
valuesBySchemaID := map[string]codersdk.TemplateVersionParameter{}
for _, parameterValue := range parameterValues {
valuesBySchemaID[parameterValue.SchemaID.String()] = parameterValue
}
sort.Slice(parameterSchemas, func(i, j int) bool {
return parameterSchemas[i].Name < parameterSchemas[j].Name
})
missingSchemas := make([]codersdk.ProjectVersionParameterSchema, 0)
missingSchemas := make([]codersdk.TemplateVersionParameterSchema, 0)
for _, parameterSchema := range parameterSchemas {
_, ok := valuesBySchemaID[parameterSchema.ID.String()]
if ok {
@ -171,7 +171,7 @@ func createValidProjectVersion(cmd *cobra.Command, client *codersdk.Client, orga
}
missingSchemas = append(missingSchemas, parameterSchema)
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("This project has required variables! They are scoped to the project, and not viewable after being set.")+"\r\n")
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("This template has required variables! They are scoped to the template, and not viewable after being set.")+"\r\n")
for _, parameterSchema := range missingSchemas {
value, err := cliui.ParameterSchema(cmd, parameterSchema)
if err != nil {
@ -185,18 +185,18 @@ func createValidProjectVersion(cmd *cobra.Command, client *codersdk.Client, orga
})
_, _ = fmt.Fprintln(cmd.OutOrStdout())
}
return createValidProjectVersion(cmd, client, organization, provisioner, hash, parameters...)
return createValidTemplateVersion(cmd, client, organization, provisioner, hash, parameters...)
}
if version.Job.Status != codersdk.ProvisionerJobSucceeded {
return nil, nil, xerrors.New(version.Job.Error)
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), cliui.Styles.Checkmark.String()+" Successfully imported project source!\n")
_, _ = fmt.Fprintf(cmd.OutOrStdout(), cliui.Styles.Checkmark.String()+" Successfully imported template source!\n")
resources, err := client.ProjectVersionResources(cmd.Context(), version.ID)
resources, err := client.TemplateVersionResources(cmd.Context(), version.ID)
if err != nil {
return nil, nil, err
}
return &version, parameters, displayProjectVersionInfo(cmd, resources)
return &version, parameters, displayTemplateVersionInfo(cmd, resources)
}

View File

@ -12,17 +12,17 @@ import (
"github.com/coder/coder/pty/ptytest"
)
func TestProjectCreate(t *testing.T) {
func TestTemplateCreate(t *testing.T) {
t.Parallel()
t.Run("Create", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
source := clitest.CreateProjectVersionSource(t, &echo.Responses{
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{
Parse: echo.ParseComplete,
Provision: echo.ProvisionComplete,
})
cmd, root := clitest.New(t, "projects", "create", "my-project", "--directory", source, "--provisioner", string(database.ProvisionerTypeEcho))
cmd, root := clitest.New(t, "templates", "create", "my-template", "--directory", source, "--provisioner", string(database.ProvisionerTypeEcho))
clitest.SetupConfig(t, client, root)
_ = coderdtest.NewProvisionerDaemon(t, client)
doneChan := make(chan struct{})
@ -35,7 +35,7 @@ func TestProjectCreate(t *testing.T) {
require.NoError(t, err)
}()
matches := []string{
"Create project?", "yes",
"Create template?", "yes",
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]

View File

@ -2,7 +2,7 @@ package cli
import "github.com/spf13/cobra"
func projectEdit() *cobra.Command {
func templateEdit() *cobra.Command {
return &cobra.Command{
Use: "edit",
RunE: func(cmd *cobra.Command, args []string) error {

View File

@ -12,10 +12,10 @@ import (
"github.com/coder/coder/provisionersdk"
)
func projectInit() *cobra.Command {
func templateInit() *cobra.Command {
return &cobra.Command{
Use: "init [directory]",
Short: "Get started with a templated project.",
Short: "Get started with a templated template.",
RunE: func(cmd *cobra.Command, args []string) error {
exampleList, err := examples.List()
if err != nil {
@ -28,7 +28,7 @@ func projectInit() *cobra.Command {
exampleByName[example.Name] = example
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Wrap.Render("Projects contain Infrastructure as Code that works with Coder to provision development workspaces. Get started by selecting an example:\n"))
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Wrap.Render("Templates contain Infrastructure as Code that works with Coder to provision development workspaces. Get started by selecting an example:\n"))
option, err := cliui.Select(cmd, cliui.SelectOptions{
Options: exampleNames,
})
@ -66,7 +66,7 @@ func projectInit() *cobra.Command {
return err
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"Inside that directory, get started by running:")
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render(cliui.Styles.Code.Render("coder projects create"))+"\n")
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render(cliui.Styles.Code.Render("coder templates create"))+"\n")
return nil
},
}

View File

@ -10,12 +10,12 @@ import (
"github.com/coder/coder/pty/ptytest"
)
func TestProjectInit(t *testing.T) {
func TestTemplateInit(t *testing.T) {
t.Parallel()
t.Run("Extract", func(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
cmd, _ := clitest.New(t, "projects", "init", tempDir)
cmd, _ := clitest.New(t, "templates", "init", tempDir)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())

View File

@ -9,7 +9,7 @@ import (
"github.com/spf13/cobra"
)
func projectList() *cobra.Command {
func templateList() *cobra.Command {
return &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
@ -23,18 +23,18 @@ func projectList() *cobra.Command {
if err != nil {
return err
}
projects, err := client.ProjectsByOrganization(cmd.Context(), organization.ID)
templates, err := client.TemplatesByOrganization(cmd.Context(), organization.ID)
if err != nil {
return err
}
if len(projects) == 0 {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s No projects found in %s! Create one:\n\n", caret, color.HiWhiteString(organization.Name))
_, _ = fmt.Fprintln(cmd.OutOrStdout(), color.HiMagentaString(" $ coder projects create <directory>\n"))
if len(templates) == 0 {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s No templates found in %s! Create one:\n\n", caret, color.HiWhiteString(organization.Name))
_, _ = fmt.Fprintln(cmd.OutOrStdout(), color.HiMagentaString(" $ coder templates create <directory>\n"))
return nil
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s Projects found in %s %s\n\n",
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s Templates found in %s %s\n\n",
caret,
color.HiWhiteString(organization.Name),
color.HiBlackString("[%dms]",
@ -42,20 +42,20 @@ func projectList() *cobra.Command {
writer := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 4, ' ', 0)
_, _ = fmt.Fprintf(writer, "%s\t%s\t%s\t%s\n",
color.HiBlackString("Project"),
color.HiBlackString("Template"),
color.HiBlackString("Source"),
color.HiBlackString("Last Updated"),
color.HiBlackString("Used By"))
for _, project := range projects {
for _, template := range templates {
suffix := ""
if project.WorkspaceOwnerCount != 1 {
if template.WorkspaceOwnerCount != 1 {
suffix = "s"
}
_, _ = fmt.Fprintf(writer, "%s\t%s\t%s\t%s\n",
color.New(color.FgHiCyan).Sprint(project.Name),
color.New(color.FgHiCyan).Sprint(template.Name),
color.WhiteString("Archive"),
color.WhiteString(project.UpdatedAt.Format("January 2, 2006")),
color.New(color.FgHiWhite).Sprintf("%d developer%s", project.WorkspaceOwnerCount, suffix))
color.WhiteString(template.UpdatedAt.Format("January 2, 2006")),
color.New(color.FgHiWhite).Sprintf("%d developer%s", template.WorkspaceOwnerCount, suffix))
}
return writer.Flush()
},

View File

@ -4,11 +4,11 @@ import (
"github.com/spf13/cobra"
)
func projectPlan() *cobra.Command {
func templatePlan() *cobra.Command {
return &cobra.Command{
Use: "plan <directory>",
Args: cobra.MinimumNArgs(1),
Short: "Plan a project update from the current directory",
Short: "Plan a template update from the current directory",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},

View File

@ -12,37 +12,37 @@ import (
"github.com/coder/coder/codersdk"
)
func projects() *cobra.Command {
func templates() *cobra.Command {
cmd := &cobra.Command{
Use: "projects",
Aliases: []string{"project"},
Use: "templates",
Aliases: []string{"template"},
Example: `
- Create a project for developers to create workspaces
- Create a template for developers to create workspaces
` + color.New(color.FgHiMagenta).Sprint("$ coder projects create") + `
` + color.New(color.FgHiMagenta).Sprint("$ coder templates create") + `
- Make changes to your project, and plan the changes
- Make changes to your template, and plan the changes
` + color.New(color.FgHiMagenta).Sprint("$ coder projects plan <name>") + `
` + color.New(color.FgHiMagenta).Sprint("$ coder templates plan <name>") + `
- Update the project. Your developers can update their workspaces
- Update the template. Your developers can update their workspaces
` + color.New(color.FgHiMagenta).Sprint("$ coder projects update <name>"),
` + color.New(color.FgHiMagenta).Sprint("$ coder templates update <name>"),
}
cmd.AddCommand(
projectCreate(),
projectEdit(),
projectInit(),
projectList(),
projectPlan(),
projectUpdate(),
projectVersions(),
templateCreate(),
templateEdit(),
templateInit(),
templateList(),
templatePlan(),
templateUpdate(),
templateVersions(),
)
return cmd
}
func displayProjectVersionInfo(cmd *cobra.Command, resources []codersdk.WorkspaceResource) error {
func displayTemplateVersionInfo(cmd *cobra.Command, resources []codersdk.WorkspaceResource) error {
sort.Slice(resources, func(i, j int) bool {
return fmt.Sprintf("%s.%s", resources[i].Type, resources[i].Name) < fmt.Sprintf("%s.%s", resources[j].Type, resources[j].Name)
})

View File

@ -14,11 +14,11 @@ import (
"github.com/coder/coder/provisionersdk"
)
func projectUpdate() *cobra.Command {
func templateUpdate() *cobra.Command {
return &cobra.Command{
Use: "update <project> [directory]",
Use: "update <template> [directory]",
Args: cobra.MinimumNArgs(1),
Short: "Update the source-code of a project from a directory.",
Short: "Update the source-code of a template from a directory.",
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
if err != nil {
@ -28,7 +28,7 @@ func projectUpdate() *cobra.Command {
if err != nil {
return err
}
project, err := client.ProjectByName(cmd.Context(), organization.ID, args[0])
template, err := client.TemplateByName(cmd.Context(), organization.ID, args[0])
if err != nil {
return err
}
@ -53,8 +53,8 @@ func projectUpdate() *cobra.Command {
}
before := time.Now()
projectVersion, err := client.CreateProjectVersion(cmd.Context(), organization.ID, codersdk.CreateProjectVersionRequest{
ProjectID: project.ID,
templateVersion, err := client.CreateTemplateVersion(cmd.Context(), organization.ID, codersdk.CreateTemplateVersionRequest{
TemplateID: template.ID,
StorageMethod: database.ProvisionerStorageMethodFile,
StorageSource: resp.Hash,
Provisioner: database.ProvisionerTypeTerraform,
@ -62,7 +62,7 @@ func projectUpdate() *cobra.Command {
if err != nil {
return err
}
logs, err := client.ProjectVersionLogsAfter(cmd.Context(), projectVersion.ID, before)
logs, err := client.TemplateVersionLogsAfter(cmd.Context(), templateVersion.ID, before)
if err != nil {
return err
}
@ -73,17 +73,17 @@ func projectUpdate() *cobra.Command {
}
_, _ = fmt.Printf("terraform (%s): %s\n", log.Level, log.Output)
}
projectVersion, err = client.ProjectVersion(cmd.Context(), projectVersion.ID)
templateVersion, err = client.TemplateVersion(cmd.Context(), templateVersion.ID)
if err != nil {
return err
}
if projectVersion.Job.Status != codersdk.ProvisionerJobSucceeded {
if templateVersion.Job.Status != codersdk.ProvisionerJobSucceeded {
return xerrors.New("job failed")
}
err = client.UpdateActiveProjectVersion(cmd.Context(), project.ID, codersdk.UpdateActiveProjectVersion{
ID: projectVersion.ID,
err = client.UpdateActiveTemplateVersion(cmd.Context(), template.ID, codersdk.UpdateActiveTemplateVersion{
ID: templateVersion.ID,
})
if err != nil {
return err

View File

@ -2,7 +2,7 @@ package cli
import "github.com/spf13/cobra"
func projectVersions() *cobra.Command {
func templateVersions() *cobra.Command {
return &cobra.Command{
Use: "versions",
Aliases: []string{"version"},
@ -12,4 +12,4 @@ func projectVersions() *cobra.Command {
}
}
// coder project versions
// coder template versions

View File

@ -24,7 +24,7 @@ func TestWorkspaceAgent(t *testing.T) {
})
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
@ -42,9 +42,9 @@ func TestWorkspaceAgent(t *testing.T) {
},
}},
})
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
cmd, _ := clitest.New(t, "workspaces", "agent", "--auth", "aws-instance-identity", "--url", client.URL.String())
@ -78,7 +78,7 @@ func TestWorkspaceAgent(t *testing.T) {
})
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
@ -96,9 +96,9 @@ func TestWorkspaceAgent(t *testing.T) {
},
}},
})
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
cmd, _ := clitest.New(t, "workspaces", "agent", "--auth", "google-instance-identity", "--url", client.URL.String())

View File

@ -18,12 +18,12 @@ import (
func workspaceCreate() *cobra.Command {
var (
projectName string
templateName string
)
cmd := &cobra.Command{
Use: "create <name>",
Args: cobra.ExactArgs(1),
Short: "Create a workspace from a project",
Short: "Create a workspace from a template",
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
if err != nil {
@ -34,36 +34,36 @@ func workspaceCreate() *cobra.Command {
return err
}
var project codersdk.Project
if projectName == "" {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Wrap.Render("Select a project:"))
var template codersdk.Template
if templateName == "" {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Wrap.Render("Select a template:"))
projectNames := []string{}
projectByName := map[string]codersdk.Project{}
projects, err := client.ProjectsByOrganization(cmd.Context(), organization.ID)
templateNames := []string{}
templateByName := map[string]codersdk.Template{}
templates, err := client.TemplatesByOrganization(cmd.Context(), organization.ID)
if err != nil {
return err
}
for _, project := range projects {
projectNames = append(projectNames, project.Name)
projectByName[project.Name] = project
for _, template := range templates {
templateNames = append(templateNames, template.Name)
templateByName[template.Name] = template
}
sort.Slice(projectNames, func(i, j int) bool {
return projectByName[projectNames[i]].WorkspaceOwnerCount > projectByName[projectNames[j]].WorkspaceOwnerCount
sort.Slice(templateNames, func(i, j int) bool {
return templateByName[templateNames[i]].WorkspaceOwnerCount > templateByName[templateNames[j]].WorkspaceOwnerCount
})
// Move the cursor up a single line for nicer display!
option, err := cliui.Select(cmd, cliui.SelectOptions{
Options: projectNames,
Options: templateNames,
HideSearch: true,
})
if err != nil {
return err
}
project = projectByName[option]
template = templateByName[option]
} else {
project, err = client.ProjectByName(cmd.Context(), organization.ID, projectName)
template, err = client.TemplateByName(cmd.Context(), organization.ID, templateName)
if err != nil {
return xerrors.Errorf("get project by name: %w", err)
return xerrors.Errorf("get template by name: %w", err)
}
if err != nil {
return err
@ -71,7 +71,7 @@ func workspaceCreate() *cobra.Command {
}
_, _ = fmt.Fprintln(cmd.OutOrStdout())
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"Creating with the "+cliui.Styles.Field.Render(project.Name)+" project...")
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"Creating with the "+cliui.Styles.Field.Render(template.Name)+" template...")
workspaceName := args[0]
_, err = client.WorkspaceByName(cmd.Context(), codersdk.Me, workspaceName)
@ -79,11 +79,11 @@ func workspaceCreate() *cobra.Command {
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
}
projectVersion, err := client.ProjectVersion(cmd.Context(), project.ActiveVersionID)
templateVersion, err := client.TemplateVersion(cmd.Context(), template.ActiveVersionID)
if err != nil {
return err
}
parameterSchemas, err := client.ProjectVersionSchema(cmd.Context(), projectVersion.ID)
parameterSchemas, err := client.TemplateVersionSchema(cmd.Context(), templateVersion.ID)
if err != nil {
return err
}
@ -95,7 +95,7 @@ func workspaceCreate() *cobra.Command {
continue
}
if !printed {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("This project has customizable parameters! These can be changed after create, but may have unintended side effects (like data loss).")+"\r\n")
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("This template has customizable parameters! These can be changed after create, but may have unintended side effects (like data loss).")+"\r\n")
printed = true
}
@ -115,11 +115,11 @@ func workspaceCreate() *cobra.Command {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.FocusedPrompt.String()+"Previewing resources...")
_, _ = fmt.Fprintln(cmd.OutOrStdout())
}
resources, err := client.ProjectVersionResources(cmd.Context(), projectVersion.ID)
resources, err := client.TemplateVersionResources(cmd.Context(), templateVersion.ID)
if err != nil {
return err
}
err = displayProjectVersionInfo(cmd, resources)
err = displayTemplateVersionInfo(cmd, resources)
if err != nil {
return err
}
@ -138,7 +138,7 @@ func workspaceCreate() *cobra.Command {
before := time.Now()
workspace, err := client.CreateWorkspace(cmd.Context(), codersdk.Me, codersdk.CreateWorkspaceRequest{
ProjectID: project.ID,
TemplateID: template.ID,
Name: workspaceName,
ParameterValues: parameters,
})
@ -167,7 +167,7 @@ func workspaceCreate() *cobra.Command {
return err
},
}
cmd.Flags().StringVarP(&projectName, "project", "p", "", "Specify a project name.")
cmd.Flags().StringVarP(&templateName, "template", "p", "", "Specify a template name.")
return cmd
}

View File

@ -17,10 +17,10 @@ func TestWorkspaceCreate(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
cmd, root := clitest.New(t, "workspaces", "create", "my-workspace", "--project", project.Name)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
cmd, root := clitest.New(t, "workspaces", "create", "my-workspace", "--template", template.Name)
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)

View File

@ -42,14 +42,14 @@ func workspaceList() *cobra.Command {
writer := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 4, ' ', 0)
_, _ = fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%s\n",
color.HiBlackString("Workspace"),
color.HiBlackString("Project"),
color.HiBlackString("Template"),
color.HiBlackString("Status"),
color.HiBlackString("Last Built"),
color.HiBlackString("Outdated"))
for _, workspace := range workspaces {
_, _ = fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%+v\n",
color.New(color.FgHiCyan).Sprint(workspace.Name),
color.WhiteString(workspace.ProjectName),
color.WhiteString(workspace.TemplateName),
color.WhiteString(string(workspace.LatestBuild.Transition)),
color.WhiteString(workspace.LatestBuild.Job.CompletedAt.Format("January 2, 2006")),
workspace.Outdated)

View File

@ -25,14 +25,14 @@ func workspaceUpdate() *cobra.Command {
_, _ = fmt.Printf("Workspace isn't outdated!\n")
return nil
}
project, err := client.Project(cmd.Context(), workspace.ProjectID)
template, err := client.Template(cmd.Context(), workspace.TemplateID)
if err != nil {
return nil
}
before := time.Now()
build, err := client.CreateWorkspaceBuild(cmd.Context(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
ProjectVersionID: project.ActiveVersionID,
Transition: workspace.LatestBuild.Transition,
TemplateVersionID: template.ActiveVersionID,
Transition: workspace.LatestBuild.Transition,
})
if err != nil {
return err

View File

@ -130,7 +130,7 @@ func parse(cmd *cobra.Command, parameters []codersdk.CreateParameterRequest) err
}
before := time.Now()
version, err := client.CreateProjectVersion(cmd.Context(), created.OrganizationID, codersdk.CreateProjectVersionRequest{
version, err := client.CreateTemplateVersion(cmd.Context(), created.OrganizationID, codersdk.CreateTemplateVersionRequest{
StorageMethod: database.ProvisionerStorageMethodFile,
StorageSource: resp.Hash,
Provisioner: database.ProvisionerTypeTerraform,
@ -139,7 +139,7 @@ func parse(cmd *cobra.Command, parameters []codersdk.CreateParameterRequest) err
if err != nil {
return err
}
logs, err := client.ProjectVersionLogsAfter(cmd.Context(), version.ID, before)
logs, err := client.TemplateVersionLogsAfter(cmd.Context(), version.ID, before)
if err != nil {
return err
}
@ -150,7 +150,7 @@ func parse(cmd *cobra.Command, parameters []codersdk.CreateParameterRequest) err
}
_, _ = fmt.Printf("terraform (%s): %s\n", log.Level, log.Output)
}
version, err = client.ProjectVersion(cmd.Context(), version.ID)
version, err = client.TemplateVersion(cmd.Context(), version.ID)
if err != nil {
return err
}
@ -158,12 +158,12 @@ func parse(cmd *cobra.Command, parameters []codersdk.CreateParameterRequest) err
return xerrors.Errorf("Job wasn't successful, it was %q. Check the logs!", version.Job.Status)
}
_, err = client.ProjectVersionResources(cmd.Context(), version.ID)
_, err = client.TemplateVersionResources(cmd.Context(), version.ID)
if err != nil {
return err
}
project, err := client.CreateProject(cmd.Context(), created.OrganizationID, codersdk.CreateProjectRequest{
template, err := client.CreateTemplate(cmd.Context(), created.OrganizationID, codersdk.CreateTemplateRequest{
Name: "test",
VersionID: version.ID,
})
@ -172,8 +172,8 @@ func parse(cmd *cobra.Command, parameters []codersdk.CreateParameterRequest) err
}
workspace, err := client.CreateWorkspace(cmd.Context(), created.UserID, codersdk.CreateWorkspaceRequest{
ProjectID: project.ID,
Name: "example",
TemplateID: template.ID,
Name: "example",
})
if err != nil {
return err
@ -205,8 +205,8 @@ func parse(cmd *cobra.Command, parameters []codersdk.CreateParameterRequest) err
}
build, err := client.CreateWorkspaceBuild(cmd.Context(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
ProjectVersionID: version.ID,
Transition: database.WorkspaceTransitionDelete,
TemplateVersionID: version.ID,
Transition: database.WorkspaceTransitionDelete,
})
if err != nil {
return err

View File

@ -76,11 +76,11 @@ func New(options *Options) (http.Handler, func()) {
)
r.Get("/", api.organization)
r.Get("/provisionerdaemons", api.provisionerDaemonsByOrganization)
r.Post("/projectversions", api.postProjectVersionsByOrganization)
r.Route("/projects", func(r chi.Router) {
r.Post("/", api.postProjectsByOrganization)
r.Get("/", api.projectsByOrganization)
r.Get("/{projectname}", api.projectByOrganizationAndName)
r.Post("/templateversions", api.postTemplateVersionsByOrganization)
r.Route("/templates", func(r chi.Router) {
r.Post("/", api.postTemplatesByOrganization)
r.Get("/", api.templatesByOrganization)
r.Get("/{templatename}", api.templateByOrganizationAndName)
})
})
r.Route("/parameters/{scope}/{id}", func(r chi.Router) {
@ -91,33 +91,33 @@ func New(options *Options) (http.Handler, func()) {
r.Delete("/", api.deleteParameter)
})
})
r.Route("/projects/{project}", func(r chi.Router) {
r.Route("/templates/{template}", func(r chi.Router) {
r.Use(
httpmw.ExtractAPIKey(options.Database, nil),
httpmw.ExtractProjectParam(options.Database),
httpmw.ExtractTemplateParam(options.Database),
httpmw.ExtractOrganizationParam(options.Database),
)
r.Get("/", api.project)
r.Delete("/", api.deleteProject)
r.Get("/", api.template)
r.Delete("/", api.deleteTemplate)
r.Route("/versions", func(r chi.Router) {
r.Get("/", api.projectVersionsByProject)
r.Patch("/", api.patchActiveProjectVersion)
r.Get("/{projectversionname}", api.projectVersionByName)
r.Get("/", api.templateVersionsByTemplate)
r.Patch("/", api.patchActiveTemplateVersion)
r.Get("/{templateversionname}", api.templateVersionByName)
})
})
r.Route("/projectversions/{projectversion}", func(r chi.Router) {
r.Route("/templateversions/{templateversion}", func(r chi.Router) {
r.Use(
httpmw.ExtractAPIKey(options.Database, nil),
httpmw.ExtractProjectVersionParam(options.Database),
httpmw.ExtractTemplateVersionParam(options.Database),
httpmw.ExtractOrganizationParam(options.Database),
)
r.Get("/", api.projectVersion)
r.Patch("/cancel", api.patchCancelProjectVersion)
r.Get("/schema", api.projectVersionSchema)
r.Get("/parameters", api.projectVersionParameters)
r.Get("/resources", api.projectVersionResources)
r.Get("/logs", api.projectVersionLogs)
r.Get("/", api.templateVersion)
r.Patch("/cancel", api.patchCancelTemplateVersion)
r.Get("/schema", api.templateVersionSchema)
r.Get("/parameters", api.templateVersionParameters)
r.Get("/resources", api.templateVersionResources)
r.Get("/logs", api.templateVersionLogs)
})
r.Route("/provisionerdaemons", func(r chi.Router) {
r.Route("/me", func(r chi.Router) {

View File

@ -203,44 +203,44 @@ func CreateAnotherUser(t *testing.T, client *codersdk.Client, organizationID uui
return other
}
// CreateProjectVersion creates a project import provisioner job
// CreateTemplateVersion creates a template import provisioner job
// with the responses provided. It uses the "echo" provisioner for compatibility
// with testing.
func CreateProjectVersion(t *testing.T, client *codersdk.Client, organizationID uuid.UUID, res *echo.Responses) codersdk.ProjectVersion {
func CreateTemplateVersion(t *testing.T, client *codersdk.Client, organizationID uuid.UUID, res *echo.Responses) codersdk.TemplateVersion {
data, err := echo.Tar(res)
require.NoError(t, err)
file, err := client.Upload(context.Background(), codersdk.ContentTypeTar, data)
require.NoError(t, err)
projectVersion, err := client.CreateProjectVersion(context.Background(), organizationID, codersdk.CreateProjectVersionRequest{
templateVersion, err := client.CreateTemplateVersion(context.Background(), organizationID, codersdk.CreateTemplateVersionRequest{
StorageSource: file.Hash,
StorageMethod: database.ProvisionerStorageMethodFile,
Provisioner: database.ProvisionerTypeEcho,
})
require.NoError(t, err)
return projectVersion
return templateVersion
}
// CreateProject creates a project with the "echo" provisioner for
// CreateTemplate creates a template with the "echo" provisioner for
// compatibility with testing. The name assigned is randomly generated.
func CreateProject(t *testing.T, client *codersdk.Client, organization uuid.UUID, version uuid.UUID) codersdk.Project {
project, err := client.CreateProject(context.Background(), organization, codersdk.CreateProjectRequest{
func CreateTemplate(t *testing.T, client *codersdk.Client, organization uuid.UUID, version uuid.UUID) codersdk.Template {
template, err := client.CreateTemplate(context.Background(), organization, codersdk.CreateTemplateRequest{
Name: randomUsername(),
VersionID: version,
})
require.NoError(t, err)
return project
return template
}
// AwaitProjectImportJob awaits for an import job to reach completed status.
func AwaitProjectVersionJob(t *testing.T, client *codersdk.Client, version uuid.UUID) codersdk.ProjectVersion {
var projectVersion codersdk.ProjectVersion
// AwaitTemplateImportJob awaits for an import job to reach completed status.
func AwaitTemplateVersionJob(t *testing.T, client *codersdk.Client, version uuid.UUID) codersdk.TemplateVersion {
var templateVersion codersdk.TemplateVersion
require.Eventually(t, func() bool {
var err error
projectVersion, err = client.ProjectVersion(context.Background(), version)
templateVersion, err = client.TemplateVersion(context.Background(), version)
require.NoError(t, err)
return projectVersion.Job.CompletedAt != nil
return templateVersion.Job.CompletedAt != nil
}, 5*time.Second, 25*time.Millisecond)
return projectVersion
return templateVersion
}
// AwaitWorkspaceBuildJob waits for a workspace provision job to reach completed status.
@ -275,12 +275,12 @@ func AwaitWorkspaceAgents(t *testing.T, client *codersdk.Client, build uuid.UUID
return resources
}
// CreateWorkspace creates a workspace for the user and project provided.
// CreateWorkspace creates a workspace for the user and template provided.
// A random name is generated for it.
func CreateWorkspace(t *testing.T, client *codersdk.Client, user uuid.UUID, projectID uuid.UUID) codersdk.Workspace {
func CreateWorkspace(t *testing.T, client *codersdk.Client, user uuid.UUID, templateID uuid.UUID) codersdk.Workspace {
workspace, err := client.CreateWorkspace(context.Background(), user, codersdk.CreateWorkspaceRequest{
ProjectID: projectID,
Name: randomUsername(),
TemplateID: templateID,
Name: randomUsername(),
})
require.NoError(t, err)
return workspace

View File

@ -18,10 +18,10 @@ func TestNew(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
closer := coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
_, _ = coderdtest.NewGoogleInstanceIdentity(t, "example", false)

View File

@ -22,8 +22,8 @@ func New() database.Store {
files: make([]database.File, 0),
parameterValue: make([]database.ParameterValue, 0),
parameterSchema: make([]database.ParameterSchema, 0),
project: make([]database.Project, 0),
projectVersion: make([]database.ProjectVersion, 0),
template: make([]database.Template, 0),
templateVersion: make([]database.TemplateVersion, 0),
provisionerDaemons: make([]database.ProvisionerDaemon, 0),
provisionerJobs: make([]database.ProvisionerJob, 0),
provisionerJobLog: make([]database.ProvisionerJobLog, 0),
@ -49,8 +49,8 @@ type fakeQuerier struct {
files []database.File
parameterValue []database.ParameterValue
parameterSchema []database.ParameterSchema
project []database.Project
projectVersion []database.ProjectVersion
template []database.Template
templateVersion []database.TemplateVersion
provisionerDaemons []database.ProvisionerDaemon
provisionerJobs []database.ProvisionerJob
provisionerJobAgent []database.WorkspaceAgent
@ -164,13 +164,13 @@ func (q *fakeQuerier) GetUserCount(_ context.Context) (int64, error) {
return int64(len(q.users)), nil
}
func (q *fakeQuerier) GetWorkspacesByProjectID(_ context.Context, arg database.GetWorkspacesByProjectIDParams) ([]database.Workspace, error) {
func (q *fakeQuerier) GetWorkspacesByTemplateID(_ context.Context, arg database.GetWorkspacesByTemplateIDParams) ([]database.Workspace, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
workspaces := make([]database.Workspace, 0)
for _, workspace := range q.workspace {
if workspace.ProjectID.String() != arg.ProjectID.String() {
if workspace.TemplateID.String() != arg.TemplateID.String() {
continue
}
if workspace.Deleted != arg.Deleted {
@ -215,38 +215,38 @@ func (q *fakeQuerier) GetWorkspaceByUserIDAndName(_ context.Context, arg databas
return database.Workspace{}, sql.ErrNoRows
}
func (q *fakeQuerier) GetWorkspaceOwnerCountsByProjectIDs(_ context.Context, projectIDs []uuid.UUID) ([]database.GetWorkspaceOwnerCountsByProjectIDsRow, error) {
func (q *fakeQuerier) GetWorkspaceOwnerCountsByTemplateIDs(_ context.Context, templateIDs []uuid.UUID) ([]database.GetWorkspaceOwnerCountsByTemplateIDsRow, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
counts := map[uuid.UUID]map[uuid.UUID]struct{}{}
for _, projectID := range projectIDs {
for _, templateID := range templateIDs {
found := false
for _, workspace := range q.workspace {
if workspace.ProjectID != projectID {
if workspace.TemplateID != templateID {
continue
}
if workspace.Deleted {
continue
}
countByOwnerID, ok := counts[projectID]
countByOwnerID, ok := counts[templateID]
if !ok {
countByOwnerID = map[uuid.UUID]struct{}{}
}
countByOwnerID[workspace.OwnerID] = struct{}{}
counts[projectID] = countByOwnerID
counts[templateID] = countByOwnerID
found = true
break
}
if !found {
counts[projectID] = map[uuid.UUID]struct{}{}
counts[templateID] = map[uuid.UUID]struct{}{}
}
}
res := make([]database.GetWorkspaceOwnerCountsByProjectIDsRow, 0)
res := make([]database.GetWorkspaceOwnerCountsByTemplateIDsRow, 0)
for key, value := range counts {
res = append(res, database.GetWorkspaceOwnerCountsByProjectIDsRow{
ProjectID: key,
Count: int64(len(value)),
res = append(res, database.GetWorkspaceOwnerCountsByTemplateIDsRow{
TemplateID: key,
Count: int64(len(value)),
})
}
if len(res) == 0 {
@ -431,47 +431,47 @@ func (q *fakeQuerier) GetParameterValuesByScope(_ context.Context, arg database.
return parameterValues, nil
}
func (q *fakeQuerier) GetProjectByID(_ context.Context, id uuid.UUID) (database.Project, error) {
func (q *fakeQuerier) GetTemplateByID(_ context.Context, id uuid.UUID) (database.Template, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
for _, project := range q.project {
if project.ID.String() == id.String() {
return project, nil
for _, template := range q.template {
if template.ID.String() == id.String() {
return template, nil
}
}
return database.Project{}, sql.ErrNoRows
return database.Template{}, sql.ErrNoRows
}
func (q *fakeQuerier) GetProjectByOrganizationAndName(_ context.Context, arg database.GetProjectByOrganizationAndNameParams) (database.Project, error) {
func (q *fakeQuerier) GetTemplateByOrganizationAndName(_ context.Context, arg database.GetTemplateByOrganizationAndNameParams) (database.Template, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
for _, project := range q.project {
if project.OrganizationID != arg.OrganizationID {
for _, template := range q.template {
if template.OrganizationID != arg.OrganizationID {
continue
}
if !strings.EqualFold(project.Name, arg.Name) {
if !strings.EqualFold(template.Name, arg.Name) {
continue
}
if project.Deleted != arg.Deleted {
if template.Deleted != arg.Deleted {
continue
}
return project, nil
return template, nil
}
return database.Project{}, sql.ErrNoRows
return database.Template{}, sql.ErrNoRows
}
func (q *fakeQuerier) GetProjectVersionsByProjectID(_ context.Context, projectID uuid.UUID) ([]database.ProjectVersion, error) {
func (q *fakeQuerier) GetTemplateVersionsByTemplateID(_ context.Context, templateID uuid.UUID) ([]database.TemplateVersion, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
version := make([]database.ProjectVersion, 0)
for _, projectVersion := range q.projectVersion {
if projectVersion.ProjectID.UUID.String() != projectID.String() {
version := make([]database.TemplateVersion, 0)
for _, templateVersion := range q.templateVersion {
if templateVersion.TemplateID.UUID.String() != templateID.String() {
continue
}
version = append(version, projectVersion)
version = append(version, templateVersion)
}
if len(version) == 0 {
return nil, sql.ErrNoRows
@ -479,46 +479,46 @@ func (q *fakeQuerier) GetProjectVersionsByProjectID(_ context.Context, projectID
return version, nil
}
func (q *fakeQuerier) GetProjectVersionByProjectIDAndName(_ context.Context, arg database.GetProjectVersionByProjectIDAndNameParams) (database.ProjectVersion, error) {
func (q *fakeQuerier) GetTemplateVersionByTemplateIDAndName(_ context.Context, arg database.GetTemplateVersionByTemplateIDAndNameParams) (database.TemplateVersion, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
for _, projectVersion := range q.projectVersion {
if projectVersion.ProjectID != arg.ProjectID {
for _, templateVersion := range q.templateVersion {
if templateVersion.TemplateID != arg.TemplateID {
continue
}
if !strings.EqualFold(projectVersion.Name, arg.Name) {
if !strings.EqualFold(templateVersion.Name, arg.Name) {
continue
}
return projectVersion, nil
return templateVersion, nil
}
return database.ProjectVersion{}, sql.ErrNoRows
return database.TemplateVersion{}, sql.ErrNoRows
}
func (q *fakeQuerier) GetProjectVersionByID(_ context.Context, projectVersionID uuid.UUID) (database.ProjectVersion, error) {
func (q *fakeQuerier) GetTemplateVersionByID(_ context.Context, templateVersionID uuid.UUID) (database.TemplateVersion, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
for _, projectVersion := range q.projectVersion {
if projectVersion.ID.String() != projectVersionID.String() {
for _, templateVersion := range q.templateVersion {
if templateVersion.ID.String() != templateVersionID.String() {
continue
}
return projectVersion, nil
return templateVersion, nil
}
return database.ProjectVersion{}, sql.ErrNoRows
return database.TemplateVersion{}, sql.ErrNoRows
}
func (q *fakeQuerier) GetProjectVersionByJobID(_ context.Context, jobID uuid.UUID) (database.ProjectVersion, error) {
func (q *fakeQuerier) GetTemplateVersionByJobID(_ context.Context, jobID uuid.UUID) (database.TemplateVersion, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
for _, projectVersion := range q.projectVersion {
if projectVersion.JobID.String() != jobID.String() {
for _, templateVersion := range q.templateVersion {
if templateVersion.JobID.String() != jobID.String() {
continue
}
return projectVersion, nil
return templateVersion, nil
}
return database.ProjectVersion{}, sql.ErrNoRows
return database.TemplateVersion{}, sql.ErrNoRows
}
func (q *fakeQuerier) GetParameterSchemasByJobID(_ context.Context, jobID uuid.UUID) ([]database.ParameterSchema, error) {
@ -557,43 +557,43 @@ func (q *fakeQuerier) GetParameterValueByScopeAndName(_ context.Context, arg dat
return database.ParameterValue{}, sql.ErrNoRows
}
func (q *fakeQuerier) GetProjectsByOrganization(_ context.Context, arg database.GetProjectsByOrganizationParams) ([]database.Project, error) {
func (q *fakeQuerier) GetTemplatesByOrganization(_ context.Context, arg database.GetTemplatesByOrganizationParams) ([]database.Template, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
projects := make([]database.Project, 0)
for _, project := range q.project {
if project.Deleted != arg.Deleted {
templates := make([]database.Template, 0)
for _, template := range q.template {
if template.Deleted != arg.Deleted {
continue
}
if project.OrganizationID != arg.OrganizationID {
if template.OrganizationID != arg.OrganizationID {
continue
}
projects = append(projects, project)
templates = append(templates, template)
}
if len(projects) == 0 {
if len(templates) == 0 {
return nil, sql.ErrNoRows
}
return projects, nil
return templates, nil
}
func (q *fakeQuerier) GetProjectsByIDs(_ context.Context, ids []uuid.UUID) ([]database.Project, error) {
func (q *fakeQuerier) GetTemplatesByIDs(_ context.Context, ids []uuid.UUID) ([]database.Template, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
projects := make([]database.Project, 0)
for _, project := range q.project {
templates := make([]database.Template, 0)
for _, template := range q.template {
for _, id := range ids {
if project.ID.String() != id.String() {
if template.ID.String() != id.String() {
continue
}
projects = append(projects, project)
templates = append(templates, template)
}
}
if len(projects) == 0 {
if len(templates) == 0 {
return nil, sql.ErrNoRows
}
return projects, nil
return templates, nil
}
func (q *fakeQuerier) GetOrganizationMemberByUserID(_ context.Context, arg database.GetOrganizationMemberByUserIDParams) (database.OrganizationMember, error) {
@ -850,12 +850,12 @@ func (q *fakeQuerier) InsertParameterValue(_ context.Context, arg database.Inser
return parameterValue, nil
}
func (q *fakeQuerier) InsertProject(_ context.Context, arg database.InsertProjectParams) (database.Project, error) {
func (q *fakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTemplateParams) (database.Template, error) {
q.mutex.Lock()
defer q.mutex.Unlock()
//nolint:gosimple
project := database.Project{
template := database.Template{
ID: arg.ID,
CreatedAt: arg.CreatedAt,
UpdatedAt: arg.UpdatedAt,
@ -864,18 +864,18 @@ func (q *fakeQuerier) InsertProject(_ context.Context, arg database.InsertProjec
Provisioner: arg.Provisioner,
ActiveVersionID: arg.ActiveVersionID,
}
q.project = append(q.project, project)
return project, nil
q.template = append(q.template, template)
return template, nil
}
func (q *fakeQuerier) InsertProjectVersion(_ context.Context, arg database.InsertProjectVersionParams) (database.ProjectVersion, error) {
func (q *fakeQuerier) InsertTemplateVersion(_ context.Context, arg database.InsertTemplateVersionParams) (database.TemplateVersion, error) {
q.mutex.Lock()
defer q.mutex.Unlock()
//nolint:gosimple
version := database.ProjectVersion{
version := database.TemplateVersion{
ID: arg.ID,
ProjectID: arg.ProjectID,
TemplateID: arg.TemplateID,
OrganizationID: arg.OrganizationID,
CreatedAt: arg.CreatedAt,
UpdatedAt: arg.UpdatedAt,
@ -883,7 +883,7 @@ func (q *fakeQuerier) InsertProjectVersion(_ context.Context, arg database.Inser
Description: arg.Description,
JobID: arg.JobID,
}
q.projectVersion = append(q.projectVersion, version)
q.templateVersion = append(q.templateVersion, version)
return version, nil
}
@ -1033,12 +1033,12 @@ func (q *fakeQuerier) InsertWorkspace(_ context.Context, arg database.InsertWork
//nolint:gosimple
workspace := database.Workspace{
ID: arg.ID,
CreatedAt: arg.CreatedAt,
UpdatedAt: arg.UpdatedAt,
OwnerID: arg.OwnerID,
ProjectID: arg.ProjectID,
Name: arg.Name,
ID: arg.ID,
CreatedAt: arg.CreatedAt,
UpdatedAt: arg.UpdatedAt,
OwnerID: arg.OwnerID,
TemplateID: arg.TemplateID,
Name: arg.Name,
}
q.workspace = append(q.workspace, workspace)
return workspace, nil
@ -1049,17 +1049,17 @@ func (q *fakeQuerier) InsertWorkspaceBuild(_ context.Context, arg database.Inser
defer q.mutex.Unlock()
workspaceBuild := database.WorkspaceBuild{
ID: arg.ID,
CreatedAt: arg.CreatedAt,
UpdatedAt: arg.UpdatedAt,
WorkspaceID: arg.WorkspaceID,
Name: arg.Name,
ProjectVersionID: arg.ProjectVersionID,
BeforeID: arg.BeforeID,
Transition: arg.Transition,
InitiatorID: arg.InitiatorID,
JobID: arg.JobID,
ProvisionerState: arg.ProvisionerState,
ID: arg.ID,
CreatedAt: arg.CreatedAt,
UpdatedAt: arg.UpdatedAt,
WorkspaceID: arg.WorkspaceID,
Name: arg.Name,
TemplateVersionID: arg.TemplateVersionID,
BeforeID: arg.BeforeID,
Transition: arg.Transition,
InitiatorID: arg.InitiatorID,
JobID: arg.JobID,
ProvisionerState: arg.ProvisionerState,
}
q.workspaceBuild = append(q.workspaceBuild, workspaceBuild)
return workspaceBuild, nil
@ -1084,47 +1084,47 @@ func (q *fakeQuerier) UpdateAPIKeyByID(_ context.Context, arg database.UpdateAPI
return sql.ErrNoRows
}
func (q *fakeQuerier) UpdateProjectActiveVersionByID(_ context.Context, arg database.UpdateProjectActiveVersionByIDParams) error {
func (q *fakeQuerier) UpdateTemplateActiveVersionByID(_ context.Context, arg database.UpdateTemplateActiveVersionByIDParams) error {
q.mutex.Lock()
defer q.mutex.Unlock()
for index, project := range q.project {
if project.ID.String() != arg.ID.String() {
for index, template := range q.template {
if template.ID.String() != arg.ID.String() {
continue
}
project.ActiveVersionID = arg.ActiveVersionID
q.project[index] = project
template.ActiveVersionID = arg.ActiveVersionID
q.template[index] = template
return nil
}
return sql.ErrNoRows
}
func (q *fakeQuerier) UpdateProjectDeletedByID(_ context.Context, arg database.UpdateProjectDeletedByIDParams) error {
func (q *fakeQuerier) UpdateTemplateDeletedByID(_ context.Context, arg database.UpdateTemplateDeletedByIDParams) error {
q.mutex.Lock()
defer q.mutex.Unlock()
for index, project := range q.project {
if project.ID.String() != arg.ID.String() {
for index, template := range q.template {
if template.ID.String() != arg.ID.String() {
continue
}
project.Deleted = arg.Deleted
q.project[index] = project
template.Deleted = arg.Deleted
q.template[index] = template
return nil
}
return sql.ErrNoRows
}
func (q *fakeQuerier) UpdateProjectVersionByID(_ context.Context, arg database.UpdateProjectVersionByIDParams) error {
func (q *fakeQuerier) UpdateTemplateVersionByID(_ context.Context, arg database.UpdateTemplateVersionByIDParams) error {
q.mutex.Lock()
defer q.mutex.Unlock()
for index, projectVersion := range q.projectVersion {
if projectVersion.ID.String() != arg.ID.String() {
for index, templateVersion := range q.templateVersion {
if templateVersion.ID.String() != arg.ID.String() {
continue
}
projectVersion.ProjectID = arg.ProjectID
projectVersion.UpdatedAt = arg.UpdatedAt
q.projectVersion[index] = projectVersion
templateVersion.TemplateID = arg.TemplateID
templateVersion.UpdatedAt = arg.UpdatedAt
q.templateVersion[index] = templateVersion
return nil
}
return sql.ErrNoRows

102
coderd/database/dump.sql generated
View File

@ -27,7 +27,7 @@ CREATE TYPE parameter_destination_scheme AS ENUM (
CREATE TYPE parameter_scope AS ENUM (
'organization',
'project',
'template',
'import_job',
'user',
'workspace'
@ -44,7 +44,7 @@ CREATE TYPE parameter_type_system AS ENUM (
);
CREATE TYPE provisioner_job_type AS ENUM (
'project_version_import',
'template_version_import',
'workspace_build'
);
@ -160,28 +160,6 @@ CREATE TABLE parameter_values (
destination_scheme parameter_destination_scheme NOT NULL
);
CREATE TABLE project_versions (
id uuid NOT NULL,
project_id uuid,
organization_id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
name character varying(64) NOT NULL,
description character varying(1048576) NOT NULL,
job_id uuid NOT NULL
);
CREATE TABLE projects (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
organization_id uuid NOT NULL,
deleted boolean DEFAULT false NOT NULL,
name character varying(64) NOT NULL,
provisioner provisioner_type NOT NULL,
active_version_id uuid NOT NULL
);
CREATE TABLE provisioner_daemons (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
@ -219,6 +197,28 @@ CREATE TABLE provisioner_jobs (
worker_id uuid
);
CREATE TABLE template_versions (
id uuid NOT NULL,
template_id uuid,
organization_id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
name character varying(64) NOT NULL,
description character varying(1048576) NOT NULL,
job_id uuid NOT NULL
);
CREATE TABLE templates (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
organization_id uuid NOT NULL,
deleted boolean DEFAULT false NOT NULL,
name character varying(64) NOT NULL,
provisioner provisioner_type NOT NULL,
active_version_id uuid NOT NULL
);
CREATE TABLE users (
id uuid NOT NULL,
email text NOT NULL,
@ -252,7 +252,7 @@ CREATE TABLE workspace_builds (
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
workspace_id uuid NOT NULL,
project_version_id uuid NOT NULL,
template_version_id uuid NOT NULL,
name character varying(64) NOT NULL,
before_id uuid,
after_id uuid,
@ -278,7 +278,7 @@ CREATE TABLE workspaces (
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
owner_id uuid NOT NULL,
project_id uuid NOT NULL,
template_id uuid NOT NULL,
deleted boolean DEFAULT false NOT NULL,
name character varying(64) NOT NULL
);
@ -315,18 +315,6 @@ ALTER TABLE ONLY parameter_values
ALTER TABLE ONLY parameter_values
ADD CONSTRAINT parameter_values_scope_id_name_key UNIQUE (scope_id, name);
ALTER TABLE ONLY project_versions
ADD CONSTRAINT project_versions_pkey PRIMARY KEY (id);
ALTER TABLE ONLY project_versions
ADD CONSTRAINT project_versions_project_id_name_key UNIQUE (project_id, name);
ALTER TABLE ONLY projects
ADD CONSTRAINT projects_organization_id_name_key UNIQUE (organization_id, name);
ALTER TABLE ONLY projects
ADD CONSTRAINT projects_pkey PRIMARY KEY (id);
ALTER TABLE ONLY provisioner_daemons
ADD CONSTRAINT provisioner_daemons_name_key UNIQUE (name);
@ -339,6 +327,18 @@ ALTER TABLE ONLY provisioner_job_logs
ALTER TABLE ONLY provisioner_jobs
ADD CONSTRAINT provisioner_jobs_pkey PRIMARY KEY (id);
ALTER TABLE ONLY template_versions
ADD CONSTRAINT template_versions_pkey PRIMARY KEY (id);
ALTER TABLE ONLY template_versions
ADD CONSTRAINT template_versions_template_id_name_key UNIQUE (template_id, name);
ALTER TABLE ONLY templates
ADD CONSTRAINT templates_organization_id_name_key UNIQUE (organization_id, name);
ALTER TABLE ONLY templates
ADD CONSTRAINT templates_pkey PRIMARY KEY (id);
ALTER TABLE ONLY users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
@ -373,7 +373,7 @@ CREATE UNIQUE INDEX idx_organization_name ON organizations USING btree (name);
CREATE UNIQUE INDEX idx_organization_name_lower ON organizations USING btree (lower(name));
CREATE UNIQUE INDEX idx_projects_name_lower ON projects USING btree (lower((name)::text));
CREATE UNIQUE INDEX idx_templates_name_lower ON templates USING btree (lower((name)::text));
CREATE UNIQUE INDEX idx_users_email ON users USING btree (email);
@ -381,7 +381,7 @@ CREATE UNIQUE INDEX idx_users_username ON users USING btree (username);
CREATE UNIQUE INDEX idx_workspaces_name_lower ON workspaces USING btree (lower((name)::text));
CREATE UNIQUE INDEX projects_organization_id_name_idx ON projects USING btree (organization_id, name) WHERE (deleted = false);
CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, name) WHERE (deleted = false);
CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username));
@ -402,21 +402,21 @@ ALTER TABLE ONLY organization_members
ALTER TABLE ONLY parameter_schemas
ADD CONSTRAINT parameter_schemas_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
ALTER TABLE ONLY project_versions
ADD CONSTRAINT project_versions_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
ALTER TABLE ONLY project_versions
ADD CONSTRAINT project_versions_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY projects
ADD CONSTRAINT projects_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
ALTER TABLE ONLY provisioner_job_logs
ADD CONSTRAINT provisioner_job_logs_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
ALTER TABLE ONLY provisioner_jobs
ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
ALTER TABLE ONLY template_versions
ADD CONSTRAINT template_versions_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
ALTER TABLE ONLY template_versions
ADD CONSTRAINT template_versions_template_id_fkey FOREIGN KEY (template_id) REFERENCES templates(id) ON DELETE CASCADE;
ALTER TABLE ONLY templates
ADD CONSTRAINT templates_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
ALTER TABLE ONLY workspace_agents
ADD CONSTRAINT workspace_agents_resource_id_fkey FOREIGN KEY (resource_id) REFERENCES workspace_resources(id) ON DELETE CASCADE;
@ -424,7 +424,7 @@ ALTER TABLE ONLY workspace_builds
ADD CONSTRAINT workspace_builds_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
ALTER TABLE ONLY workspace_builds
ADD CONSTRAINT workspace_builds_project_version_id_fkey FOREIGN KEY (project_version_id) REFERENCES project_versions(id) ON DELETE CASCADE;
ADD CONSTRAINT workspace_builds_template_version_id_fkey FOREIGN KEY (template_version_id) REFERENCES template_versions(id) ON DELETE CASCADE;
ALTER TABLE ONLY workspace_builds
ADD CONSTRAINT workspace_builds_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE;
@ -436,5 +436,5 @@ ALTER TABLE ONLY workspaces
ADD CONSTRAINT workspaces_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT;
ALTER TABLE ONLY workspaces
ADD CONSTRAINT workspaces_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE RESTRICT;
ADD CONSTRAINT workspaces_template_id_fkey FOREIGN KEY (template_id) REFERENCES templates(id) ON DELETE RESTRICT;

View File

@ -1,6 +0,0 @@
DROP TABLE project_versions;
DROP TABLE projects;
DROP TYPE provisioner_type;
DROP TABLE files;

View File

@ -0,0 +1,6 @@
DROP TABLE template_versions;
DROP TABLE templates;
DROP TYPE provisioner_type;
DROP TABLE files;

View File

@ -1,4 +1,4 @@
-- Store arbitrary data like project source code or avatars.
-- Store arbitrary data like template source code or avatars.
CREATE TABLE files (
hash varchar(64) NOT NULL,
created_at timestamptz NOT NULL,
@ -11,37 +11,37 @@ CREATE TABLE files (
CREATE TYPE provisioner_type AS ENUM ('echo', 'terraform');
-- Project defines infrastructure that your software project
-- Template defines infrastructure that your software template
-- requires for development.
CREATE TABLE projects (
CREATE TABLE templates (
id uuid NOT NULL,
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL,
-- Projects must be scoped to an organization.
-- Templates must be scoped to an organization.
organization_id uuid NOT NULL REFERENCES organizations (id) ON DELETE CASCADE,
deleted boolean NOT NULL DEFAULT FALSE,
name varchar(64) NOT NULL,
provisioner provisioner_type NOT NULL,
-- Target's a Project Version to use for Workspaces.
-- Target's a Template Version to use for Workspaces.
-- If a Workspace doesn't match this version, it will be prompted to rebuild.
active_version_id uuid NOT NULL,
PRIMARY KEY (id),
-- Disallow projects to have the same name under
-- Disallow templates to have the same name under
-- the same organization.
UNIQUE(organization_id, name)
);
-- Enforces no active projects have the same name.
CREATE UNIQUE INDEX ON projects (organization_id, name) WHERE deleted = FALSE;
CREATE UNIQUE INDEX idx_projects_name_lower ON projects USING btree (lower(name));
-- Enforces no active templates have the same name.
CREATE UNIQUE INDEX ON templates (organization_id, name) WHERE deleted = FALSE;
CREATE UNIQUE INDEX idx_templates_name_lower ON templates USING btree (lower(name));
-- Project Versions store historical project data. When a Project Version is imported,
-- an "import" job is queued to parse parameters. A Project Version
-- Template Versions store historical template data. When a Template Version is imported,
-- an "import" job is queued to parse parameters. A Template Version
-- can only be used if the import job succeeds.
CREATE TABLE project_versions (
CREATE TABLE template_versions (
id uuid NOT NULL,
-- This should be indexed. It is intentionally nullable.
project_id uuid REFERENCES projects (id) ON DELETE CASCADE,
template_id uuid REFERENCES templates (id) ON DELETE CASCADE,
organization_id uuid NOT NULL REFERENCES organizations (id) ON DELETE CASCADE,
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL,
@ -51,10 +51,10 @@ CREATE TABLE project_versions (
-- Extracted from a README.md on import.
-- Maximum of 1MB.
description varchar(1048576) NOT NULL,
-- The job ID for building the project version.
-- The job ID for building the template version.
job_id uuid NOT NULL,
PRIMARY KEY (id),
-- Disallow projects to have the same build name
-- Disallow templates to have the same build name
-- multiple times.
UNIQUE(project_id, name)
UNIQUE(template_id, name)
);

View File

@ -5,7 +5,7 @@ CREATE TABLE workspaces (
-- Use ON DELETE RESTRICT so that we can cleanup external workspace
-- resources first.
owner_id uuid NOT NULL REFERENCES users (id) ON DELETE RESTRICT,
project_id uuid NOT NULL REFERENCES projects (id) ON DELETE RESTRICT,
template_id uuid NOT NULL REFERENCES templates (id) ON DELETE RESTRICT,
deleted boolean NOT NULL DEFAULT FALSE,
name varchar(64) NOT NULL,
PRIMARY KEY (id)

View File

@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS provisioner_daemons (
);
CREATE TYPE provisioner_job_type AS ENUM (
'project_version_import',
'template_version_import',
'workspace_build'
);
@ -92,7 +92,7 @@ CREATE TABLE workspace_agents (
CREATE TYPE parameter_scope AS ENUM (
'organization',
'project',
'template',
'import_job',
'user',
'workspace'
@ -107,7 +107,7 @@ CREATE TYPE parameter_source_scheme AS ENUM('none', 'data');
-- Supported schemes for a parameter destination.
CREATE TYPE parameter_destination_scheme AS ENUM('none', 'environment_variable', 'provisioner_variable');
-- Stores project version parameters parsed on import.
-- Stores template version parameters parsed on import.
-- No secrets are stored here.
--
-- All parameter validation occurs server-side to process
@ -162,7 +162,7 @@ CREATE TABLE workspace_builds (
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL,
workspace_id uuid NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE,
project_version_id uuid NOT NULL REFERENCES project_versions (id) ON DELETE CASCADE,
template_version_id uuid NOT NULL REFERENCES template_versions (id) ON DELETE CASCADE,
name varchar(64) NOT NULL,
before_id uuid,
after_id uuid,

View File

@ -97,7 +97,7 @@ type ParameterScope string
const (
ParameterScopeOrganization ParameterScope = "organization"
ParameterScopeProject ParameterScope = "project"
ParameterScopeTemplate ParameterScope = "template"
ParameterScopeImportJob ParameterScope = "import_job"
ParameterScopeUser ParameterScope = "user"
ParameterScopeWorkspace ParameterScope = "workspace"
@ -156,8 +156,8 @@ func (e *ParameterTypeSystem) Scan(src interface{}) error {
type ProvisionerJobType string
const (
ProvisionerJobTypeProjectVersionImport ProvisionerJobType = "project_version_import"
ProvisionerJobTypeWorkspaceBuild ProvisionerJobType = "workspace_build"
ProvisionerJobTypeTemplateVersionImport ProvisionerJobType = "template_version_import"
ProvisionerJobTypeWorkspaceBuild ProvisionerJobType = "workspace_build"
)
func (e *ProvisionerJobType) Scan(src interface{}) error {
@ -316,28 +316,6 @@ type ParameterValue struct {
DestinationScheme ParameterDestinationScheme `db:"destination_scheme" json:"destination_scheme"`
}
type Project struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
Deleted bool `db:"deleted" json:"deleted"`
Name string `db:"name" json:"name"`
Provisioner ProvisionerType `db:"provisioner" json:"provisioner"`
ActiveVersionID uuid.UUID `db:"active_version_id" json:"active_version_id"`
}
type ProjectVersion struct {
ID uuid.UUID `db:"id" json:"id"`
ProjectID uuid.NullUUID `db:"project_id" json:"project_id"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Name string `db:"name" json:"name"`
Description string `db:"description" json:"description"`
JobID uuid.UUID `db:"job_id" json:"job_id"`
}
type ProvisionerDaemon struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
@ -375,6 +353,28 @@ type ProvisionerJobLog struct {
Output string `db:"output" json:"output"`
}
type Template struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
Deleted bool `db:"deleted" json:"deleted"`
Name string `db:"name" json:"name"`
Provisioner ProvisionerType `db:"provisioner" json:"provisioner"`
ActiveVersionID uuid.UUID `db:"active_version_id" json:"active_version_id"`
}
type TemplateVersion struct {
ID uuid.UUID `db:"id" json:"id"`
TemplateID uuid.NullUUID `db:"template_id" json:"template_id"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Name string `db:"name" json:"name"`
Description string `db:"description" json:"description"`
JobID uuid.UUID `db:"job_id" json:"job_id"`
}
type User struct {
ID uuid.UUID `db:"id" json:"id"`
Email string `db:"email" json:"email"`
@ -388,13 +388,13 @@ type User struct {
}
type Workspace struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
ProjectID uuid.UUID `db:"project_id" json:"project_id"`
Deleted bool `db:"deleted" json:"deleted"`
Name string `db:"name" json:"name"`
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
Deleted bool `db:"deleted" json:"deleted"`
Name string `db:"name" json:"name"`
}
type WorkspaceAgent struct {
@ -414,18 +414,18 @@ type WorkspaceAgent struct {
}
type WorkspaceBuild struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
ProjectVersionID uuid.UUID `db:"project_version_id" json:"project_version_id"`
Name string `db:"name" json:"name"`
BeforeID uuid.NullUUID `db:"before_id" json:"before_id"`
AfterID uuid.NullUUID `db:"after_id" json:"after_id"`
Transition WorkspaceTransition `db:"transition" json:"transition"`
InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"`
ProvisionerState []byte `db:"provisioner_state" json:"provisioner_state"`
JobID uuid.UUID `db:"job_id" json:"job_id"`
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"`
Name string `db:"name" json:"name"`
BeforeID uuid.NullUUID `db:"before_id" json:"before_id"`
AfterID uuid.NullUUID `db:"after_id" json:"after_id"`
Transition WorkspaceTransition `db:"transition" json:"transition"`
InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"`
ProvisionerState []byte `db:"provisioner_state" json:"provisioner_state"`
JobID uuid.UUID `db:"job_id" json:"job_id"`
}
type WorkspaceResource struct {

View File

@ -22,19 +22,19 @@ type querier interface {
GetParameterSchemasByJobID(ctx context.Context, jobID uuid.UUID) ([]ParameterSchema, error)
GetParameterValueByScopeAndName(ctx context.Context, arg GetParameterValueByScopeAndNameParams) (ParameterValue, error)
GetParameterValuesByScope(ctx context.Context, arg GetParameterValuesByScopeParams) ([]ParameterValue, error)
GetProjectByID(ctx context.Context, id uuid.UUID) (Project, error)
GetProjectByOrganizationAndName(ctx context.Context, arg GetProjectByOrganizationAndNameParams) (Project, error)
GetProjectVersionByID(ctx context.Context, id uuid.UUID) (ProjectVersion, error)
GetProjectVersionByJobID(ctx context.Context, jobID uuid.UUID) (ProjectVersion, error)
GetProjectVersionByProjectIDAndName(ctx context.Context, arg GetProjectVersionByProjectIDAndNameParams) (ProjectVersion, error)
GetProjectVersionsByProjectID(ctx context.Context, dollar_1 uuid.UUID) ([]ProjectVersion, error)
GetProjectsByIDs(ctx context.Context, ids []uuid.UUID) ([]Project, error)
GetProjectsByOrganization(ctx context.Context, arg GetProjectsByOrganizationParams) ([]Project, error)
GetProvisionerDaemonByID(ctx context.Context, id uuid.UUID) (ProvisionerDaemon, error)
GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, error)
GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (ProvisionerJob, error)
GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]ProvisionerJob, error)
GetProvisionerLogsByIDBetween(ctx context.Context, arg GetProvisionerLogsByIDBetweenParams) ([]ProvisionerJobLog, error)
GetTemplateByID(ctx context.Context, id uuid.UUID) (Template, error)
GetTemplateByOrganizationAndName(ctx context.Context, arg GetTemplateByOrganizationAndNameParams) (Template, error)
GetTemplateVersionByID(ctx context.Context, id uuid.UUID) (TemplateVersion, error)
GetTemplateVersionByJobID(ctx context.Context, jobID uuid.UUID) (TemplateVersion, error)
GetTemplateVersionByTemplateIDAndName(ctx context.Context, arg GetTemplateVersionByTemplateIDAndNameParams) (TemplateVersion, error)
GetTemplateVersionsByTemplateID(ctx context.Context, dollar_1 uuid.UUID) ([]TemplateVersion, error)
GetTemplatesByIDs(ctx context.Context, ids []uuid.UUID) ([]Template, error)
GetTemplatesByOrganization(ctx context.Context, arg GetTemplatesByOrganizationParams) ([]Template, error)
GetUserByEmailOrUsername(ctx context.Context, arg GetUserByEmailOrUsernameParams) (User, error)
GetUserByID(ctx context.Context, id uuid.UUID) (User, error)
GetUserCount(ctx context.Context) (int64, error)
@ -49,10 +49,10 @@ type querier interface {
GetWorkspaceBuildsByWorkspaceIDsWithoutAfter(ctx context.Context, ids []uuid.UUID) ([]WorkspaceBuild, error)
GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Workspace, error)
GetWorkspaceByUserIDAndName(ctx context.Context, arg GetWorkspaceByUserIDAndNameParams) (Workspace, error)
GetWorkspaceOwnerCountsByProjectIDs(ctx context.Context, ids []uuid.UUID) ([]GetWorkspaceOwnerCountsByProjectIDsRow, error)
GetWorkspaceOwnerCountsByTemplateIDs(ctx context.Context, ids []uuid.UUID) ([]GetWorkspaceOwnerCountsByTemplateIDsRow, error)
GetWorkspaceResourceByID(ctx context.Context, id uuid.UUID) (WorkspaceResource, error)
GetWorkspaceResourcesByJobID(ctx context.Context, jobID uuid.UUID) ([]WorkspaceResource, error)
GetWorkspacesByProjectID(ctx context.Context, arg GetWorkspacesByProjectIDParams) ([]Workspace, error)
GetWorkspacesByTemplateID(ctx context.Context, arg GetWorkspacesByTemplateIDParams) ([]Workspace, error)
GetWorkspacesByUserID(ctx context.Context, arg GetWorkspacesByUserIDParams) ([]Workspace, error)
InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (APIKey, error)
InsertFile(ctx context.Context, arg InsertFileParams) (File, error)
@ -61,11 +61,11 @@ type querier interface {
InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error)
InsertParameterSchema(ctx context.Context, arg InsertParameterSchemaParams) (ParameterSchema, error)
InsertParameterValue(ctx context.Context, arg InsertParameterValueParams) (ParameterValue, error)
InsertProject(ctx context.Context, arg InsertProjectParams) (Project, error)
InsertProjectVersion(ctx context.Context, arg InsertProjectVersionParams) (ProjectVersion, error)
InsertProvisionerDaemon(ctx context.Context, arg InsertProvisionerDaemonParams) (ProvisionerDaemon, error)
InsertProvisionerJob(ctx context.Context, arg InsertProvisionerJobParams) (ProvisionerJob, error)
InsertProvisionerJobLogs(ctx context.Context, arg InsertProvisionerJobLogsParams) ([]ProvisionerJobLog, error)
InsertTemplate(ctx context.Context, arg InsertTemplateParams) (Template, error)
InsertTemplateVersion(ctx context.Context, arg InsertTemplateVersionParams) (TemplateVersion, error)
InsertUser(ctx context.Context, arg InsertUserParams) (User, error)
InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (Workspace, error)
InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error)
@ -73,13 +73,13 @@ type querier interface {
InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error)
UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDParams) error
UpdateGitSSHKey(ctx context.Context, arg UpdateGitSSHKeyParams) error
UpdateProjectActiveVersionByID(ctx context.Context, arg UpdateProjectActiveVersionByIDParams) error
UpdateProjectDeletedByID(ctx context.Context, arg UpdateProjectDeletedByIDParams) error
UpdateProjectVersionByID(ctx context.Context, arg UpdateProjectVersionByIDParams) error
UpdateProvisionerDaemonByID(ctx context.Context, arg UpdateProvisionerDaemonByIDParams) error
UpdateProvisionerJobByID(ctx context.Context, arg UpdateProvisionerJobByIDParams) error
UpdateProvisionerJobWithCancelByID(ctx context.Context, arg UpdateProvisionerJobWithCancelByIDParams) error
UpdateProvisionerJobWithCompleteByID(ctx context.Context, arg UpdateProvisionerJobWithCompleteByIDParams) error
UpdateTemplateActiveVersionByID(ctx context.Context, arg UpdateTemplateActiveVersionByIDParams) error
UpdateTemplateDeletedByID(ctx context.Context, arg UpdateTemplateDeletedByIDParams) error
UpdateTemplateVersionByID(ctx context.Context, arg UpdateTemplateVersionByIDParams) error
UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg UpdateWorkspaceAgentConnectionByIDParams) error
UpdateWorkspaceBuildByID(ctx context.Context, arg UpdateWorkspaceBuildByIDParams) error
UpdateWorkspaceDeletedByID(ctx context.Context, arg UpdateWorkspaceDeletedByIDParams) error

File diff suppressed because it is too large Load Diff

View File

@ -1,56 +0,0 @@
-- name: GetProjectVersionsByProjectID :many
SELECT
*
FROM
project_versions
WHERE
project_id = $1 :: uuid;
-- name: GetProjectVersionByJobID :one
SELECT
*
FROM
project_versions
WHERE
job_id = $1;
-- name: GetProjectVersionByProjectIDAndName :one
SELECT
*
FROM
project_versions
WHERE
project_id = $1
AND "name" = $2;
-- name: GetProjectVersionByID :one
SELECT
*
FROM
project_versions
WHERE
id = $1;
-- name: InsertProjectVersion :one
INSERT INTO
project_versions (
id,
project_id,
organization_id,
created_at,
updated_at,
"name",
description,
job_id
)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *;
-- name: UpdateProjectVersionByID :exec
UPDATE
project_versions
SET
project_id = $2,
updated_at = $3
WHERE
id = $1;

View File

@ -1,26 +1,26 @@
-- name: GetProjectByID :one
-- name: GetTemplateByID :one
SELECT
*
FROM
projects
templates
WHERE
id = $1
LIMIT
1;
-- name: GetProjectsByIDs :many
-- name: GetTemplatesByIDs :many
SELECT
*
FROM
projects
templates
WHERE
id = ANY(@ids :: uuid [ ]);
-- name: GetProjectByOrganizationAndName :one
-- name: GetTemplateByOrganizationAndName :one
SELECT
*
FROM
projects
templates
WHERE
organization_id = @organization_id
AND deleted = @deleted
@ -28,18 +28,18 @@ WHERE
LIMIT
1;
-- name: GetProjectsByOrganization :many
-- name: GetTemplatesByOrganization :many
SELECT
*
FROM
projects
templates
WHERE
organization_id = $1
AND deleted = $2;
-- name: InsertProject :one
-- name: InsertTemplate :one
INSERT INTO
projects (
templates (
id,
created_at,
updated_at,
@ -51,17 +51,17 @@ INSERT INTO
VALUES
($1, $2, $3, $4, $5, $6, $7) RETURNING *;
-- name: UpdateProjectActiveVersionByID :exec
-- name: UpdateTemplateActiveVersionByID :exec
UPDATE
projects
templates
SET
active_version_id = $2
WHERE
id = $1;
-- name: UpdateProjectDeletedByID :exec
-- name: UpdateTemplateDeletedByID :exec
UPDATE
projects
templates
SET
deleted = $2
WHERE

View File

@ -0,0 +1,56 @@
-- name: GetTemplateVersionsByTemplateID :many
SELECT
*
FROM
template_versions
WHERE
template_id = $1 :: uuid;
-- name: GetTemplateVersionByJobID :one
SELECT
*
FROM
template_versions
WHERE
job_id = $1;
-- name: GetTemplateVersionByTemplateIDAndName :one
SELECT
*
FROM
template_versions
WHERE
template_id = $1
AND "name" = $2;
-- name: GetTemplateVersionByID :one
SELECT
*
FROM
template_versions
WHERE
id = $1;
-- name: InsertTemplateVersion :one
INSERT INTO
template_versions (
id,
template_id,
organization_id,
created_at,
updated_at,
"name",
description,
job_id
)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *;
-- name: UpdateTemplateVersionByID :exec
UPDATE
template_versions
SET
template_id = $2,
updated_at = $3
WHERE
id = $1;

View File

@ -62,7 +62,7 @@ INSERT INTO
created_at,
updated_at,
workspace_id,
project_version_id,
template_version_id,
before_id,
"name",
transition,

View File

@ -8,13 +8,13 @@ WHERE
LIMIT
1;
-- name: GetWorkspacesByProjectID :many
-- name: GetWorkspacesByTemplateID :many
SELECT
*
FROM
workspaces
WHERE
project_id = $1
template_id = $1
AND deleted = $2;
-- name: GetWorkspacesByUserID :many
@ -36,16 +36,16 @@ WHERE
AND deleted = @deleted
AND LOWER("name") = LOWER(@name);
-- name: GetWorkspaceOwnerCountsByProjectIDs :many
-- name: GetWorkspaceOwnerCountsByTemplateIDs :many
SELECT
project_id,
template_id,
COUNT(DISTINCT owner_id)
FROM
workspaces
WHERE
project_id = ANY(@ids :: uuid [ ])
template_id = ANY(@ids :: uuid [ ])
GROUP BY
project_id,
template_id,
owner_id;
-- name: InsertWorkspace :one
@ -55,7 +55,7 @@ INSERT INTO
created_at,
updated_at,
owner_id,
project_id,
template_id,
name
)
VALUES

View File

@ -86,7 +86,7 @@ func TestAgentGitSSHKey(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client)
daemonCloser := coderdtest.NewProvisionerDaemon(t, client)
authToken := uuid.NewString()
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionDryRun: echo.ProvisionComplete,
Provision: []*proto.Provision_Response{{
@ -106,8 +106,8 @@ func TestAgentGitSSHKey(t *testing.T) {
},
}},
})
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
daemonCloser.Close()

View File

@ -1,53 +0,0 @@
package httpmw
import (
"context"
"database/sql"
"errors"
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/httpapi"
)
type projectParamContextKey struct{}
// ProjectParam returns the project from the ExtractProjectParam handler.
func ProjectParam(r *http.Request) database.Project {
project, ok := r.Context().Value(projectParamContextKey{}).(database.Project)
if !ok {
panic("developer error: project param middleware not provided")
}
return project
}
// ExtractProjectParam grabs a project from the "project" URL parameter.
func ExtractProjectParam(db database.Store) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
projectID, parsed := parseUUID(rw, r, "project")
if !parsed {
return
}
project, err := db.GetProjectByID(r.Context(), projectID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
Message: fmt.Sprintf("project %q does not exist", projectID),
})
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project: %s", err),
})
return
}
ctx := context.WithValue(r.Context(), projectParamContextKey{}, project)
chi.RouteContext(ctx).URLParams.Add("organization", project.OrganizationID.String())
next.ServeHTTP(rw, r.WithContext(ctx))
})
}
}

View File

@ -1,54 +0,0 @@
package httpmw
import (
"context"
"database/sql"
"errors"
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/httpapi"
)
type projectVersionParamContextKey struct{}
// ProjectVersionParam returns the project version from the ExtractProjectVersionParam handler.
func ProjectVersionParam(r *http.Request) database.ProjectVersion {
projectVersion, ok := r.Context().Value(projectVersionParamContextKey{}).(database.ProjectVersion)
if !ok {
panic("developer error: project version param middleware not provided")
}
return projectVersion
}
// ExtractProjectVersionParam grabs project version from the "projectversion" URL parameter.
func ExtractProjectVersionParam(db database.Store) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
projectVersionID, parsed := parseUUID(rw, r, "projectversion")
if !parsed {
return
}
projectVersion, err := db.GetProjectVersionByID(r.Context(), projectVersionID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
Message: fmt.Sprintf("project version %q does not exist", projectVersionID),
})
return
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project version: %s", err.Error()),
})
return
}
ctx := context.WithValue(r.Context(), projectVersionParamContextKey{}, projectVersion)
chi.RouteContext(ctx).URLParams.Add("organization", projectVersion.OrganizationID.String())
next.ServeHTTP(rw, r.WithContext(ctx))
})
}
}

View File

@ -0,0 +1,53 @@
package httpmw
import (
"context"
"database/sql"
"errors"
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/httpapi"
)
type templateParamContextKey struct{}
// TemplateParam returns the template from the ExtractTemplateParam handler.
func TemplateParam(r *http.Request) database.Template {
template, ok := r.Context().Value(templateParamContextKey{}).(database.Template)
if !ok {
panic("developer error: template param middleware not provided")
}
return template
}
// ExtractTemplateParam grabs a template from the "template" URL parameter.
func ExtractTemplateParam(db database.Store) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
templateID, parsed := parseUUID(rw, r, "template")
if !parsed {
return
}
template, err := db.GetTemplateByID(r.Context(), templateID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
Message: fmt.Sprintf("template %q does not exist", templateID),
})
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get template: %s", err),
})
return
}
ctx := context.WithValue(r.Context(), templateParamContextKey{}, template)
chi.RouteContext(ctx).URLParams.Add("organization", template.OrganizationID.String())
next.ServeHTTP(rw, r.WithContext(ctx))
})
}
}

View File

@ -19,7 +19,7 @@ import (
"github.com/coder/coder/cryptorand"
)
func TestProjectParam(t *testing.T) {
func TestTemplateParam(t *testing.T) {
t.Parallel()
setupAuthentication := func(db database.Store) (*http.Request, database.Organization) {
@ -84,7 +84,7 @@ func TestProjectParam(t *testing.T) {
t.Parallel()
db := databasefake.New()
rtr := chi.NewRouter()
rtr.Use(httpmw.ExtractProjectParam(db))
rtr.Use(httpmw.ExtractTemplateParam(db))
rtr.Get("/", nil)
r, _ := setupAuthentication(db)
rw := httptest.NewRecorder()
@ -99,11 +99,11 @@ func TestProjectParam(t *testing.T) {
t.Parallel()
db := databasefake.New()
rtr := chi.NewRouter()
rtr.Use(httpmw.ExtractProjectParam(db))
rtr.Use(httpmw.ExtractTemplateParam(db))
rtr.Get("/", nil)
r, _ := setupAuthentication(db)
chi.RouteContext(r.Context()).URLParams.Add("project", uuid.NewString())
chi.RouteContext(r.Context()).URLParams.Add("template", uuid.NewString())
rw := httptest.NewRecorder()
rtr.ServeHTTP(rw, r)
@ -116,11 +116,11 @@ func TestProjectParam(t *testing.T) {
t.Parallel()
db := databasefake.New()
rtr := chi.NewRouter()
rtr.Use(httpmw.ExtractProjectParam(db))
rtr.Use(httpmw.ExtractTemplateParam(db))
rtr.Get("/", nil)
r, _ := setupAuthentication(db)
chi.RouteContext(r.Context()).URLParams.Add("project", "not-a-uuid")
chi.RouteContext(r.Context()).URLParams.Add("template", "not-a-uuid")
rw := httptest.NewRecorder()
rtr.ServeHTTP(rw, r)
@ -129,28 +129,28 @@ func TestProjectParam(t *testing.T) {
require.Equal(t, http.StatusBadRequest, res.StatusCode)
})
t.Run("Project", func(t *testing.T) {
t.Run("Template", func(t *testing.T) {
t.Parallel()
db := databasefake.New()
rtr := chi.NewRouter()
rtr.Use(
httpmw.ExtractAPIKey(db, nil),
httpmw.ExtractProjectParam(db),
httpmw.ExtractTemplateParam(db),
httpmw.ExtractOrganizationParam(db),
)
rtr.Get("/", func(rw http.ResponseWriter, r *http.Request) {
_ = httpmw.ProjectParam(r)
_ = httpmw.TemplateParam(r)
rw.WriteHeader(http.StatusOK)
})
r, org := setupAuthentication(db)
project, err := db.InsertProject(context.Background(), database.InsertProjectParams{
template, err := db.InsertTemplate(context.Background(), database.InsertTemplateParams{
ID: uuid.New(),
OrganizationID: org.ID,
Name: "moo",
})
require.NoError(t, err)
chi.RouteContext(r.Context()).URLParams.Add("project", project.ID.String())
chi.RouteContext(r.Context()).URLParams.Add("template", template.ID.String())
rw := httptest.NewRecorder()
rtr.ServeHTTP(rw, r)

View File

@ -0,0 +1,54 @@
package httpmw
import (
"context"
"database/sql"
"errors"
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/httpapi"
)
type templateVersionParamContextKey struct{}
// TemplateVersionParam returns the template version from the ExtractTemplateVersionParam handler.
func TemplateVersionParam(r *http.Request) database.TemplateVersion {
templateVersion, ok := r.Context().Value(templateVersionParamContextKey{}).(database.TemplateVersion)
if !ok {
panic("developer error: template version param middleware not provided")
}
return templateVersion
}
// ExtractTemplateVersionParam grabs template version from the "templateversion" URL parameter.
func ExtractTemplateVersionParam(db database.Store) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
templateVersionID, parsed := parseUUID(rw, r, "templateversion")
if !parsed {
return
}
templateVersion, err := db.GetTemplateVersionByID(r.Context(), templateVersionID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
Message: fmt.Sprintf("template version %q does not exist", templateVersionID),
})
return
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get template version: %s", err.Error()),
})
return
}
ctx := context.WithValue(r.Context(), templateVersionParamContextKey{}, templateVersion)
chi.RouteContext(ctx).URLParams.Add("organization", templateVersion.OrganizationID.String())
next.ServeHTTP(rw, r.WithContext(ctx))
})
}
}

View File

@ -19,10 +19,10 @@ import (
"github.com/coder/coder/cryptorand"
)
func TestProjectVersionParam(t *testing.T) {
func TestTemplateVersionParam(t *testing.T) {
t.Parallel()
setupAuthentication := func(db database.Store) (*http.Request, database.Project) {
setupAuthentication := func(db database.Store) (*http.Request, database.Template) {
var (
id, secret = randomAPIKeyParts()
hashed = sha256.Sum256([]byte(secret))
@ -75,7 +75,7 @@ func TestProjectVersionParam(t *testing.T) {
})
require.NoError(t, err)
project, err := db.InsertProject(context.Background(), database.InsertProjectParams{
template, err := db.InsertTemplate(context.Background(), database.InsertTemplateParams{
ID: uuid.New(),
OrganizationID: organization.ID,
Name: "moo",
@ -84,16 +84,16 @@ func TestProjectVersionParam(t *testing.T) {
ctx := chi.NewRouteContext()
ctx.URLParams.Add("organization", organization.Name)
ctx.URLParams.Add("project", project.Name)
ctx.URLParams.Add("template", template.Name)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, ctx))
return r, project
return r, template
}
t.Run("None", func(t *testing.T) {
t.Parallel()
db := databasefake.New()
rtr := chi.NewRouter()
rtr.Use(httpmw.ExtractProjectVersionParam(db))
rtr.Use(httpmw.ExtractTemplateVersionParam(db))
rtr.Get("/", nil)
r, _ := setupAuthentication(db)
rw := httptest.NewRecorder()
@ -108,11 +108,11 @@ func TestProjectVersionParam(t *testing.T) {
t.Parallel()
db := databasefake.New()
rtr := chi.NewRouter()
rtr.Use(httpmw.ExtractProjectVersionParam(db))
rtr.Use(httpmw.ExtractTemplateVersionParam(db))
rtr.Get("/", nil)
r, _ := setupAuthentication(db)
chi.RouteContext(r.Context()).URLParams.Add("projectversion", uuid.NewString())
chi.RouteContext(r.Context()).URLParams.Add("templateversion", uuid.NewString())
rw := httptest.NewRecorder()
rtr.ServeHTTP(rw, r)
@ -121,28 +121,28 @@ func TestProjectVersionParam(t *testing.T) {
require.Equal(t, http.StatusNotFound, res.StatusCode)
})
t.Run("ProjectVersion", func(t *testing.T) {
t.Run("TemplateVersion", func(t *testing.T) {
t.Parallel()
db := databasefake.New()
rtr := chi.NewRouter()
rtr.Use(
httpmw.ExtractAPIKey(db, nil),
httpmw.ExtractProjectVersionParam(db),
httpmw.ExtractTemplateVersionParam(db),
httpmw.ExtractOrganizationParam(db),
)
rtr.Get("/", func(rw http.ResponseWriter, r *http.Request) {
_ = httpmw.ProjectVersionParam(r)
_ = httpmw.TemplateVersionParam(r)
rw.WriteHeader(http.StatusOK)
})
r, project := setupAuthentication(db)
projectVersion, err := db.InsertProjectVersion(context.Background(), database.InsertProjectVersionParams{
r, template := setupAuthentication(db)
templateVersion, err := db.InsertTemplateVersion(context.Background(), database.InsertTemplateVersionParams{
ID: uuid.New(),
OrganizationID: project.OrganizationID,
OrganizationID: template.OrganizationID,
Name: "moo",
})
require.NoError(t, err)
chi.RouteContext(r.Context()).URLParams.Add("projectversion", projectVersion.ID.String())
chi.RouteContext(r.Context()).URLParams.Add("templateversion", templateVersion.ID.String())
rw := httptest.NewRecorder()
rtr.ServeHTTP(rw, r)

View File

@ -58,10 +58,10 @@ func TestWorkspaceBuildParam(t *testing.T) {
require.NoError(t, err)
workspace, err := db.InsertWorkspace(context.Background(), database.InsertWorkspaceParams{
ID: uuid.New(),
ProjectID: uuid.New(),
OwnerID: user.ID,
Name: "potato",
ID: uuid.New(),
TemplateID: uuid.New(),
OwnerID: user.ID,
Name: "potato",
})
require.NoError(t, err)

View File

@ -15,7 +15,7 @@ import (
type workspaceResourceParamContextKey struct{}
// ProvisionerJobParam returns the project from the ExtractProjectParam handler.
// ProvisionerJobParam returns the template from the ExtractTemplateParam handler.
func WorkspaceResourceParam(r *http.Request) database.WorkspaceResource {
resource, ok := r.Context().Value(workspaceResourceParamContextKey{}).(database.WorkspaceResource)
if !ok {

View File

@ -88,7 +88,7 @@ func TestWorkspaceResourceParam(t *testing.T) {
rw.WriteHeader(http.StatusOK)
})
r, job := setup(db, database.ProvisionerJobTypeProjectVersionImport)
r, job := setup(db, database.ProvisionerJobTypeTemplateVersionImport)
chi.RouteContext(r.Context()).URLParams.Add("workspaceresource", job.ID.String())
rw := httptest.NewRecorder()
rtr.ServeHTTP(rw, r)

View File

@ -42,25 +42,25 @@ func (api *api) provisionerDaemonsByOrganization(rw http.ResponseWriter, r *http
render.JSON(rw, r, daemons)
}
// Creates a new version of a project. An import job is queued to parse the storage method provided.
func (api *api) postProjectVersionsByOrganization(rw http.ResponseWriter, r *http.Request) {
// Creates a new version of a template. An import job is queued to parse the storage method provided.
func (api *api) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *http.Request) {
apiKey := httpmw.APIKey(r)
organization := httpmw.OrganizationParam(r)
var req codersdk.CreateProjectVersionRequest
var req codersdk.CreateTemplateVersionRequest
if !httpapi.Read(rw, r, &req) {
return
}
if req.ProjectID != uuid.Nil {
_, err := api.Database.GetProjectByID(r.Context(), req.ProjectID)
if req.TemplateID != uuid.Nil {
_, err := api.Database.GetTemplateByID(r.Context(), req.TemplateID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
Message: "project does not exist",
Message: "template does not exist",
})
return
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project: %s", err),
Message: fmt.Sprintf("get template: %s", err),
})
return
}
@ -80,7 +80,7 @@ func (api *api) postProjectVersionsByOrganization(rw http.ResponseWriter, r *htt
return
}
var projectVersion database.ProjectVersion
var templateVersion database.TemplateVersion
var provisionerJob database.ProvisionerJob
err = api.Database.InTx(func(db database.Store) error {
jobID := uuid.New()
@ -110,24 +110,24 @@ func (api *api) postProjectVersionsByOrganization(rw http.ResponseWriter, r *htt
Provisioner: req.Provisioner,
StorageMethod: database.ProvisionerStorageMethodFile,
StorageSource: file.Hash,
Type: database.ProvisionerJobTypeProjectVersionImport,
Type: database.ProvisionerJobTypeTemplateVersionImport,
Input: []byte{'{', '}'},
})
if err != nil {
return xerrors.Errorf("insert provisioner job: %w", err)
}
var projectID uuid.NullUUID
if req.ProjectID != uuid.Nil {
projectID = uuid.NullUUID{
UUID: req.ProjectID,
var templateID uuid.NullUUID
if req.TemplateID != uuid.Nil {
templateID = uuid.NullUUID{
UUID: req.TemplateID,
Valid: true,
}
}
projectVersion, err = api.Database.InsertProjectVersion(r.Context(), database.InsertProjectVersionParams{
templateVersion, err = api.Database.InsertTemplateVersion(r.Context(), database.InsertTemplateVersionParams{
ID: uuid.New(),
ProjectID: projectID,
TemplateID: templateID,
OrganizationID: organization.ID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
@ -136,7 +136,7 @@ func (api *api) postProjectVersionsByOrganization(rw http.ResponseWriter, r *htt
JobID: provisionerJob.ID,
})
if err != nil {
return xerrors.Errorf("insert project version: %w", err)
return xerrors.Errorf("insert template version: %w", err)
}
return nil
})
@ -148,23 +148,23 @@ func (api *api) postProjectVersionsByOrganization(rw http.ResponseWriter, r *htt
}
render.Status(r, http.StatusCreated)
render.JSON(rw, r, convertProjectVersion(projectVersion, convertProvisionerJob(provisionerJob)))
render.JSON(rw, r, convertTemplateVersion(templateVersion, convertProvisionerJob(provisionerJob)))
}
// Create a new project in an organization.
func (api *api) postProjectsByOrganization(rw http.ResponseWriter, r *http.Request) {
var createProject codersdk.CreateProjectRequest
if !httpapi.Read(rw, r, &createProject) {
// Create a new template in an organization.
func (api *api) postTemplatesByOrganization(rw http.ResponseWriter, r *http.Request) {
var createTemplate codersdk.CreateTemplateRequest
if !httpapi.Read(rw, r, &createTemplate) {
return
}
organization := httpmw.OrganizationParam(r)
_, err := api.Database.GetProjectByOrganizationAndName(r.Context(), database.GetProjectByOrganizationAndNameParams{
_, err := api.Database.GetTemplateByOrganizationAndName(r.Context(), database.GetTemplateByOrganizationAndNameParams{
OrganizationID: organization.ID,
Name: createProject.Name,
Name: createTemplate.Name,
})
if err == nil {
httpapi.Write(rw, http.StatusConflict, httpapi.Response{
Message: fmt.Sprintf("project %q already exists", createProject.Name),
Message: fmt.Sprintf("template %q already exists", createTemplate.Name),
Errors: []httpapi.Error{{
Field: "name",
Code: "exists",
@ -174,23 +174,23 @@ func (api *api) postProjectsByOrganization(rw http.ResponseWriter, r *http.Reque
}
if !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project by name: %s", err),
Message: fmt.Sprintf("get template by name: %s", err),
})
return
}
projectVersion, err := api.Database.GetProjectVersionByID(r.Context(), createProject.VersionID)
templateVersion, err := api.Database.GetTemplateVersionByID(r.Context(), createTemplate.VersionID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
Message: "project version does not exist",
Message: "template version does not exist",
})
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project version by id: %s", err),
Message: fmt.Sprintf("get template version by id: %s", err),
})
return
}
importJob, err := api.Database.GetProvisionerJobByID(r.Context(), projectVersion.JobID)
importJob, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get import job by id: %s", err),
@ -198,41 +198,41 @@ func (api *api) postProjectsByOrganization(rw http.ResponseWriter, r *http.Reque
return
}
var project codersdk.Project
var template codersdk.Template
err = api.Database.InTx(func(db database.Store) error {
now := database.Now()
dbProject, err := db.InsertProject(r.Context(), database.InsertProjectParams{
dbTemplate, err := db.InsertTemplate(r.Context(), database.InsertTemplateParams{
ID: uuid.New(),
CreatedAt: now,
UpdatedAt: now,
OrganizationID: organization.ID,
Name: createProject.Name,
Name: createTemplate.Name,
Provisioner: importJob.Provisioner,
ActiveVersionID: projectVersion.ID,
ActiveVersionID: templateVersion.ID,
})
if err != nil {
return xerrors.Errorf("insert project: %s", err)
return xerrors.Errorf("insert template: %s", err)
}
err = db.UpdateProjectVersionByID(r.Context(), database.UpdateProjectVersionByIDParams{
ID: projectVersion.ID,
ProjectID: uuid.NullUUID{
UUID: dbProject.ID,
err = db.UpdateTemplateVersionByID(r.Context(), database.UpdateTemplateVersionByIDParams{
ID: templateVersion.ID,
TemplateID: uuid.NullUUID{
UUID: dbTemplate.ID,
Valid: true,
},
})
if err != nil {
return xerrors.Errorf("insert project version: %s", err)
return xerrors.Errorf("insert template version: %s", err)
}
for _, parameterValue := range createProject.ParameterValues {
for _, parameterValue := range createTemplate.ParameterValues {
_, err = db.InsertParameterValue(r.Context(), database.InsertParameterValueParams{
ID: uuid.New(),
Name: parameterValue.Name,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
Scope: database.ParameterScopeProject,
ScopeID: dbProject.ID,
Scope: database.ParameterScopeTemplate,
ScopeID: dbTemplate.ID,
SourceScheme: parameterValue.SourceScheme,
SourceValue: parameterValue.SourceValue,
DestinationScheme: parameterValue.DestinationScheme,
@ -242,7 +242,7 @@ func (api *api) postProjectsByOrganization(rw http.ResponseWriter, r *http.Reque
}
}
project = convertProject(dbProject, 0)
template = convertTemplate(dbTemplate, 0)
return nil
})
if err != nil {
@ -253,12 +253,12 @@ func (api *api) postProjectsByOrganization(rw http.ResponseWriter, r *http.Reque
}
render.Status(r, http.StatusCreated)
render.JSON(rw, r, project)
render.JSON(rw, r, template)
}
func (api *api) projectsByOrganization(rw http.ResponseWriter, r *http.Request) {
func (api *api) templatesByOrganization(rw http.ResponseWriter, r *http.Request) {
organization := httpmw.OrganizationParam(r)
projects, err := api.Database.GetProjectsByOrganization(r.Context(), database.GetProjectsByOrganizationParams{
templates, err := api.Database.GetTemplatesByOrganization(r.Context(), database.GetTemplatesByOrganizationParams{
OrganizationID: organization.ID,
})
if errors.Is(err, sql.ErrNoRows) {
@ -266,15 +266,15 @@ func (api *api) projectsByOrganization(rw http.ResponseWriter, r *http.Request)
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get projects: %s", err.Error()),
Message: fmt.Sprintf("get templates: %s", err.Error()),
})
return
}
projectIDs := make([]uuid.UUID, 0, len(projects))
for _, project := range projects {
projectIDs = append(projectIDs, project.ID)
templateIDs := make([]uuid.UUID, 0, len(templates))
for _, template := range templates {
templateIDs = append(templateIDs, template.ID)
}
workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByProjectIDs(r.Context(), projectIDs)
workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByTemplateIDs(r.Context(), templateIDs)
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
@ -285,31 +285,31 @@ func (api *api) projectsByOrganization(rw http.ResponseWriter, r *http.Request)
return
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, convertProjects(projects, workspaceCounts))
render.JSON(rw, r, convertTemplates(templates, workspaceCounts))
}
func (api *api) projectByOrganizationAndName(rw http.ResponseWriter, r *http.Request) {
func (api *api) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Request) {
organization := httpmw.OrganizationParam(r)
projectName := chi.URLParam(r, "projectname")
project, err := api.Database.GetProjectByOrganizationAndName(r.Context(), database.GetProjectByOrganizationAndNameParams{
templateName := chi.URLParam(r, "templatename")
template, err := api.Database.GetTemplateByOrganizationAndName(r.Context(), database.GetTemplateByOrganizationAndNameParams{
OrganizationID: organization.ID,
Name: projectName,
Name: templateName,
})
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
Message: fmt.Sprintf("no project found by name %q in the %q organization", projectName, organization.Name),
Message: fmt.Sprintf("no template found by name %q in the %q organization", templateName, organization.Name),
})
return
}
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project by organization and name: %s", err),
Message: fmt.Sprintf("get template by organization and name: %s", err),
})
return
}
workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByProjectIDs(r.Context(), []uuid.UUID{project.ID})
workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByTemplateIDs(r.Context(), []uuid.UUID{template.ID})
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
@ -326,7 +326,7 @@ func (api *api) projectByOrganizationAndName(rw http.ResponseWriter, r *http.Req
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, convertProject(project, count))
render.JSON(rw, r, convertTemplate(template, count))
}
// convertOrganization consumes the database representation and outputs an API friendly representation.

View File

@ -32,15 +32,15 @@ func TestProvisionerDaemonsByOrganization(t *testing.T) {
})
}
func TestPostProjectVersionsByOrganization(t *testing.T) {
func TestPostTemplateVersionsByOrganization(t *testing.T) {
t.Parallel()
t.Run("InvalidProject", func(t *testing.T) {
t.Run("InvalidTemplate", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
projectID := uuid.New()
_, err := client.CreateProjectVersion(context.Background(), user.OrganizationID, codersdk.CreateProjectVersionRequest{
ProjectID: projectID,
templateID := uuid.New()
_, err := client.CreateTemplateVersion(context.Background(), user.OrganizationID, codersdk.CreateTemplateVersionRequest{
TemplateID: templateID,
StorageMethod: database.ProvisionerStorageMethodFile,
StorageSource: "hash",
Provisioner: database.ProvisionerTypeEcho,
@ -54,7 +54,7 @@ func TestPostProjectVersionsByOrganization(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_, err := client.CreateProjectVersion(context.Background(), user.OrganizationID, codersdk.CreateProjectVersionRequest{
_, err := client.CreateTemplateVersion(context.Background(), user.OrganizationID, codersdk.CreateTemplateVersionRequest{
StorageMethod: database.ProvisionerStorageMethodFile,
StorageSource: "hash",
Provisioner: database.ProvisionerTypeEcho,
@ -76,7 +76,7 @@ func TestPostProjectVersionsByOrganization(t *testing.T) {
require.NoError(t, err)
file, err := client.Upload(context.Background(), codersdk.ContentTypeTar, data)
require.NoError(t, err)
_, err = client.CreateProjectVersion(context.Background(), user.OrganizationID, codersdk.CreateProjectVersionRequest{
_, err = client.CreateTemplateVersion(context.Background(), user.OrganizationID, codersdk.CreateTemplateVersionRequest{
StorageMethod: database.ProvisionerStorageMethodFile,
StorageSource: file.Hash,
Provisioner: database.ProvisionerTypeEcho,
@ -91,24 +91,24 @@ func TestPostProjectVersionsByOrganization(t *testing.T) {
})
}
func TestPostProjectsByOrganization(t *testing.T) {
func TestPostTemplatesByOrganization(t *testing.T) {
t.Parallel()
t.Run("Create", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
})
t.Run("AlreadyExists", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
_, err := client.CreateProject(context.Background(), user.OrganizationID, codersdk.CreateProjectRequest{
Name: project.Name,
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
_, err := client.CreateTemplate(context.Background(), user.OrganizationID, codersdk.CreateTemplateRequest{
Name: template.Name,
VersionID: version.ID,
})
var apiErr *codersdk.Error
@ -120,7 +120,7 @@ func TestPostProjectsByOrganization(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_, err := client.CreateProject(context.Background(), user.OrganizationID, codersdk.CreateProjectRequest{
_, err := client.CreateTemplate(context.Background(), user.OrganizationID, codersdk.CreateTemplateRequest{
Name: "test",
VersionID: uuid.New(),
})
@ -130,48 +130,48 @@ func TestPostProjectsByOrganization(t *testing.T) {
})
}
func TestProjectsByOrganization(t *testing.T) {
func TestTemplatesByOrganization(t *testing.T) {
t.Parallel()
t.Run("ListEmpty", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
projects, err := client.ProjectsByOrganization(context.Background(), user.OrganizationID)
templates, err := client.TemplatesByOrganization(context.Background(), user.OrganizationID)
require.NoError(t, err)
require.NotNil(t, projects)
require.Len(t, projects, 0)
require.NotNil(t, templates)
require.Len(t, templates, 0)
})
t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
projects, err := client.ProjectsByOrganization(context.Background(), user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
templates, err := client.TemplatesByOrganization(context.Background(), user.OrganizationID)
require.NoError(t, err)
require.Len(t, projects, 1)
require.Len(t, templates, 1)
})
t.Run("ListMultiple", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
projects, err := client.ProjectsByOrganization(context.Background(), user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
templates, err := client.TemplatesByOrganization(context.Background(), user.OrganizationID)
require.NoError(t, err)
require.Len(t, projects, 2)
require.Len(t, templates, 2)
})
}
func TestProjectByOrganizationAndName(t *testing.T) {
func TestTemplateByOrganizationAndName(t *testing.T) {
t.Parallel()
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_, err := client.ProjectByName(context.Background(), user.OrganizationID, "something")
_, err := client.TemplateByName(context.Background(), user.OrganizationID, "something")
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
@ -181,9 +181,9 @@ func TestProjectByOrganizationAndName(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
_, err := client.ProjectByName(context.Background(), user.OrganizationID, project.Name)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
_, err := client.TemplateByName(context.Background(), user.OrganizationID, template.Name)
require.NoError(t, err)
})
}

View File

@ -13,11 +13,11 @@ import (
// ComputeScope targets identifiers to pull parameters from.
type ComputeScope struct {
ProjectImportJobID uuid.UUID
OrganizationID uuid.UUID
UserID uuid.UUID
ProjectID uuid.NullUUID
WorkspaceID uuid.NullUUID
TemplateImportJobID uuid.UUID
OrganizationID uuid.UUID
UserID uuid.UUID
TemplateID uuid.NullUUID
WorkspaceID uuid.NullUUID
}
type ComputeOptions struct {
@ -48,12 +48,12 @@ func Compute(ctx context.Context, db database.Store, scope ComputeScope, options
}
// All parameters for the import job ID!
parameterSchemas, err := db.GetParameterSchemasByJobID(ctx, scope.ProjectImportJobID)
parameterSchemas, err := db.GetParameterSchemasByJobID(ctx, scope.TemplateImportJobID)
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
return nil, xerrors.Errorf("get project parameters: %w", err)
return nil, xerrors.Errorf("get template parameters: %w", err)
}
for _, parameterSchema := range parameterSchemas {
compute.parameterSchemasByName[parameterSchema.Name] = parameterSchema
@ -71,13 +71,13 @@ func Compute(ctx context.Context, db database.Store, scope ComputeScope, options
// Job parameters come second!
err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{
Scope: database.ParameterScopeImportJob,
ScopeID: scope.ProjectImportJobID,
ScopeID: scope.TemplateImportJobID,
})
if err != nil {
return nil, err
}
// Default project parameter values come second!
// Default template parameter values come second!
for _, parameterSchema := range parameterSchemas {
if parameterSchema.DefaultSourceScheme == database.ParameterSourceSchemeNone {
continue
@ -101,21 +101,21 @@ func Compute(ctx context.Context, db database.Store, scope ComputeScope, options
DestinationScheme: parameterSchema.DefaultDestinationScheme,
SourceValue: parameterSchema.DefaultSourceValue,
Scope: database.ParameterScopeImportJob,
ScopeID: scope.ProjectImportJobID,
ScopeID: scope.TemplateImportJobID,
}, true)
if err != nil {
return nil, xerrors.Errorf("insert default value: %w", err)
}
default:
return nil, xerrors.Errorf("unsupported source scheme for project version parameter %q: %q", parameterSchema.Name, string(parameterSchema.DefaultSourceScheme))
return nil, xerrors.Errorf("unsupported source scheme for template version parameter %q: %q", parameterSchema.Name, string(parameterSchema.DefaultSourceScheme))
}
}
if scope.ProjectID.Valid {
// Project parameters come third!
if scope.TemplateID.Valid {
// Template parameters come third!
err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{
Scope: database.ParameterScopeProject,
ScopeID: scope.ProjectID.UUID,
Scope: database.ParameterScopeTemplate,
ScopeID: scope.TemplateID.UUID,
})
if err != nil {
return nil, err
@ -178,14 +178,14 @@ func (c *compute) injectScope(ctx context.Context, scopeParams database.GetParam
func (c *compute) injectSingle(scopedParameter database.ParameterValue, defaultValue bool) error {
parameterSchema, hasParameterSchema := c.parameterSchemasByName[scopedParameter.Name]
if !hasParameterSchema {
// Don't inject parameters that aren't defined by the project.
// Don't inject parameters that aren't defined by the template.
return nil
}
_, hasParameterValue := c.computedParameterByName[scopedParameter.Name]
if hasParameterValue {
if !parameterSchema.AllowOverrideSource &&
// Users and workspaces cannot override anything on a project!
// Users and workspaces cannot override anything on a template!
(scopedParameter.Scope == database.ParameterScopeUser ||
scopedParameter.Scope == database.ParameterScopeWorkspace) {
return nil

View File

@ -17,9 +17,9 @@ func TestCompute(t *testing.T) {
t.Parallel()
generateScope := func() parameter.ComputeScope {
return parameter.ComputeScope{
ProjectImportJobID: uuid.New(),
OrganizationID: uuid.New(),
ProjectID: uuid.NullUUID{
TemplateImportJobID: uuid.New(),
OrganizationID: uuid.New(),
TemplateID: uuid.NullUUID{
UUID: uuid.New(),
Valid: true,
},
@ -34,7 +34,7 @@ func TestCompute(t *testing.T) {
AllowOverrideSource bool
AllowOverrideDestination bool
DefaultDestinationScheme database.ParameterDestinationScheme
ProjectImportJobID uuid.UUID
TemplateImportJobID uuid.UUID
}
generateParameter := func(t *testing.T, db database.Store, opts parameterOptions) database.ParameterSchema {
if opts.DefaultDestinationScheme == "" {
@ -47,7 +47,7 @@ func TestCompute(t *testing.T) {
param, err := db.InsertParameterSchema(context.Background(), database.InsertParameterSchemaParams{
ID: uuid.New(),
Name: name,
JobID: opts.ProjectImportJobID,
JobID: opts.TemplateImportJobID,
DefaultSourceScheme: database.ParameterSourceSchemeData,
DefaultSourceValue: sourceValue,
AllowOverrideSource: opts.AllowOverrideSource,
@ -64,7 +64,7 @@ func TestCompute(t *testing.T) {
scope := generateScope()
_, err := db.InsertParameterSchema(context.Background(), database.InsertParameterSchemaParams{
ID: uuid.New(),
JobID: scope.ProjectImportJobID,
JobID: scope.TemplateImportJobID,
Name: "hey",
DefaultSourceScheme: database.ParameterSourceSchemeNone,
})
@ -74,12 +74,12 @@ func TestCompute(t *testing.T) {
require.Len(t, computed, 0)
})
t.Run("UseDefaultProjectValue", func(t *testing.T) {
t.Run("UseDefaultTemplateValue", func(t *testing.T) {
t.Parallel()
db := databasefake.New()
scope := generateScope()
parameterSchema := generateParameter(t, db, parameterOptions{
ProjectImportJobID: scope.ProjectImportJobID,
TemplateImportJobID: scope.TemplateImportJobID,
DefaultDestinationScheme: database.ParameterDestinationSchemeProvisionerVariable,
})
computed, err := parameter.Compute(context.Background(), db, scope, nil)
@ -88,7 +88,7 @@ func TestCompute(t *testing.T) {
computedValue := computed[0]
require.True(t, computedValue.DefaultSourceValue)
require.Equal(t, database.ParameterScopeImportJob, computedValue.Scope)
require.Equal(t, scope.ProjectImportJobID, computedValue.ScopeID)
require.Equal(t, scope.TemplateImportJobID, computedValue.ScopeID)
require.Equal(t, computedValue.SourceValue, parameterSchema.DefaultSourceValue)
})
@ -97,7 +97,7 @@ func TestCompute(t *testing.T) {
db := databasefake.New()
scope := generateScope()
parameterSchema := generateParameter(t, db, parameterOptions{
ProjectImportJobID: scope.ProjectImportJobID,
TemplateImportJobID: scope.TemplateImportJobID,
})
_, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{
ID: uuid.New(),
@ -114,7 +114,7 @@ func TestCompute(t *testing.T) {
ID: uuid.New(),
Name: parameterSchema.Name,
Scope: database.ParameterScopeImportJob,
ScopeID: scope.ProjectImportJobID,
ScopeID: scope.TemplateImportJobID,
SourceScheme: database.ParameterSourceSchemeData,
SourceValue: "secondnop",
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
@ -128,18 +128,18 @@ func TestCompute(t *testing.T) {
require.Equal(t, value.SourceValue, computed[0].SourceValue)
})
t.Run("ProjectOverridesProjectDefault", func(t *testing.T) {
t.Run("TemplateOverridesTemplateDefault", func(t *testing.T) {
t.Parallel()
db := databasefake.New()
scope := generateScope()
parameterSchema := generateParameter(t, db, parameterOptions{
ProjectImportJobID: scope.ProjectImportJobID,
TemplateImportJobID: scope.TemplateImportJobID,
})
value, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{
ID: uuid.New(),
Name: parameterSchema.Name,
Scope: database.ParameterScopeProject,
ScopeID: scope.ProjectID.UUID,
Scope: database.ParameterScopeTemplate,
ScopeID: scope.TemplateID.UUID,
SourceScheme: database.ParameterSourceSchemeData,
SourceValue: "nop",
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
@ -153,12 +153,12 @@ func TestCompute(t *testing.T) {
require.Equal(t, value.SourceValue, computed[0].SourceValue)
})
t.Run("WorkspaceCannotOverwriteProjectDefault", func(t *testing.T) {
t.Run("WorkspaceCannotOverwriteTemplateDefault", func(t *testing.T) {
t.Parallel()
db := databasefake.New()
scope := generateScope()
parameterSchema := generateParameter(t, db, parameterOptions{
ProjectImportJobID: scope.ProjectImportJobID,
TemplateImportJobID: scope.TemplateImportJobID,
})
_, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{
@ -178,13 +178,13 @@ func TestCompute(t *testing.T) {
require.Equal(t, true, computed[0].DefaultSourceValue)
})
t.Run("WorkspaceOverwriteProjectDefault", func(t *testing.T) {
t.Run("WorkspaceOverwriteTemplateDefault", func(t *testing.T) {
t.Parallel()
db := databasefake.New()
scope := generateScope()
parameterSchema := generateParameter(t, db, parameterOptions{
AllowOverrideSource: true,
ProjectImportJobID: scope.ProjectImportJobID,
TemplateImportJobID: scope.TemplateImportJobID,
})
_, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{
ID: uuid.New(),
@ -208,7 +208,7 @@ func TestCompute(t *testing.T) {
db := databasefake.New()
scope := generateScope()
_ = generateParameter(t, db, parameterOptions{
ProjectImportJobID: scope.ProjectImportJobID,
TemplateImportJobID: scope.TemplateImportJobID,
DefaultDestinationScheme: database.ParameterDestinationSchemeProvisionerVariable,
})
computed, err := parameter.Compute(context.Background(), db, scope, &parameter.ComputeOptions{

View File

@ -143,8 +143,8 @@ func readScopeAndID(rw http.ResponseWriter, r *http.Request) (database.Parameter
switch chi.URLParam(r, "scope") {
case string(codersdk.ParameterOrganization):
scope = database.ParameterScopeOrganization
case string(codersdk.ParameterProject):
scope = database.ParameterScopeProject
case string(codersdk.ParameterTemplate):
scope = database.ParameterScopeTemplate
case string(codersdk.ParameterUser):
scope = database.ParameterScopeUser
case string(codersdk.ParameterWorkspace):

View File

@ -1,225 +0,0 @@
package coderd
import (
"database/sql"
"errors"
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/google/uuid"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/coderd/httpmw"
"github.com/coder/coder/codersdk"
)
// Returns a single project.
func (api *api) project(rw http.ResponseWriter, r *http.Request) {
project := httpmw.ProjectParam(r)
workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByProjectIDs(r.Context(), []uuid.UUID{project.ID})
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get workspace counts: %s", err.Error()),
})
return
}
count := uint32(0)
if len(workspaceCounts) > 0 {
count = uint32(workspaceCounts[0].Count)
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, convertProject(project, count))
}
func (api *api) deleteProject(rw http.ResponseWriter, r *http.Request) {
project := httpmw.ProjectParam(r)
workspaces, err := api.Database.GetWorkspacesByProjectID(r.Context(), database.GetWorkspacesByProjectIDParams{
ProjectID: project.ID,
})
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get workspaces by project id: %s", err),
})
return
}
if len(workspaces) > 0 {
httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{
Message: "All workspaces must be deleted before a project can be removed.",
})
return
}
err = api.Database.UpdateProjectDeletedByID(r.Context(), database.UpdateProjectDeletedByIDParams{
ID: project.ID,
Deleted: true,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("update project deleted by id: %s", err),
})
return
}
httpapi.Write(rw, http.StatusOK, httpapi.Response{
Message: "Project has been deleted!",
})
}
func (api *api) projectVersionsByProject(rw http.ResponseWriter, r *http.Request) {
project := httpmw.ProjectParam(r)
versions, err := api.Database.GetProjectVersionsByProjectID(r.Context(), project.ID)
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project version: %s", err),
})
return
}
jobIDs := make([]uuid.UUID, 0, len(versions))
for _, version := range versions {
jobIDs = append(jobIDs, version.JobID)
}
jobs, err := api.Database.GetProvisionerJobsByIDs(r.Context(), jobIDs)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get jobs: %s", err),
})
return
}
jobByID := map[string]database.ProvisionerJob{}
for _, job := range jobs {
jobByID[job.ID.String()] = job
}
apiVersion := make([]codersdk.ProjectVersion, 0)
for _, version := range versions {
job, exists := jobByID[version.JobID.String()]
if !exists {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("job %q doesn't exist for version %q", version.JobID, version.ID),
})
return
}
apiVersion = append(apiVersion, convertProjectVersion(version, convertProvisionerJob(job)))
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, apiVersion)
}
func (api *api) projectVersionByName(rw http.ResponseWriter, r *http.Request) {
project := httpmw.ProjectParam(r)
projectVersionName := chi.URLParam(r, "projectversionname")
projectVersion, err := api.Database.GetProjectVersionByProjectIDAndName(r.Context(), database.GetProjectVersionByProjectIDAndNameParams{
ProjectID: uuid.NullUUID{
UUID: project.ID,
Valid: true,
},
Name: projectVersionName,
})
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
Message: fmt.Sprintf("no project version found by name %q", projectVersionName),
})
return
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project version by name: %s", err),
})
return
}
job, err := api.Database.GetProvisionerJobByID(r.Context(), projectVersion.JobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get provisioner job: %s", err),
})
return
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, convertProjectVersion(projectVersion, convertProvisionerJob(job)))
}
func (api *api) patchActiveProjectVersion(rw http.ResponseWriter, r *http.Request) {
var req codersdk.UpdateActiveProjectVersion
if !httpapi.Read(rw, r, &req) {
return
}
project := httpmw.ProjectParam(r)
version, err := api.Database.GetProjectVersionByID(r.Context(), req.ID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
Message: "project version not found",
})
return
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project version: %s", err),
})
return
}
if version.ProjectID.UUID.String() != project.ID.String() {
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
Message: "The provided project version doesn't belong to the specified project.",
})
return
}
err = api.Database.UpdateProjectActiveVersionByID(r.Context(), database.UpdateProjectActiveVersionByIDParams{
ID: project.ID,
ActiveVersionID: req.ID,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("update active project version: %s", err),
})
return
}
httpapi.Write(rw, http.StatusOK, httpapi.Response{
Message: "Updated the active project version!",
})
}
func convertProjects(projects []database.Project, workspaceCounts []database.GetWorkspaceOwnerCountsByProjectIDsRow) []codersdk.Project {
apiProjects := make([]codersdk.Project, 0, len(projects))
for _, project := range projects {
found := false
for _, workspaceCount := range workspaceCounts {
if workspaceCount.ProjectID.String() != project.ID.String() {
continue
}
apiProjects = append(apiProjects, convertProject(project, uint32(workspaceCount.Count)))
found = true
break
}
if !found {
apiProjects = append(apiProjects, convertProject(project, uint32(0)))
}
}
return apiProjects
}
func convertProject(project database.Project, workspaceOwnerCount uint32) codersdk.Project {
return codersdk.Project{
ID: project.ID,
CreatedAt: project.CreatedAt,
UpdatedAt: project.UpdatedAt,
OrganizationID: project.OrganizationID,
Name: project.Name,
Provisioner: project.Provisioner,
ActiveVersionID: project.ActiveVersionID,
WorkspaceOwnerCount: workspaceOwnerCount,
}
}

View File

@ -1,139 +0,0 @@
package coderd_test
import (
"context"
"net/http"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
)
func TestProject(t *testing.T) {
t.Parallel()
t.Run("Get", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
_, err := client.Project(context.Background(), project.ID)
require.NoError(t, err)
})
}
func TestDeleteProject(t *testing.T) {
t.Parallel()
t.Run("NoWorkspaces", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
err := client.DeleteProject(context.Background(), project.ID)
require.NoError(t, err)
})
t.Run("Workspaces", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
err := client.DeleteProject(context.Background(), project.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
})
}
func TestProjectVersionsByProject(t *testing.T) {
t.Parallel()
t.Run("Get", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
versions, err := client.ProjectVersionsByProject(context.Background(), project.ID)
require.NoError(t, err)
require.Len(t, versions, 1)
})
}
func TestProjectVersionByName(t *testing.T) {
t.Parallel()
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
_, err := client.ProjectVersionByName(context.Background(), project.ID, "nothing")
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
})
t.Run("Found", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
_, err := client.ProjectVersionByName(context.Background(), project.ID, version.Name)
require.NoError(t, err)
})
}
func TestPatchActiveProjectVersion(t *testing.T) {
t.Parallel()
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
err := client.UpdateActiveProjectVersion(context.Background(), project.ID, codersdk.UpdateActiveProjectVersion{
ID: uuid.New(),
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
})
t.Run("DoesNotBelong", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
version = coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
err := client.UpdateActiveProjectVersion(context.Background(), project.ID, codersdk.UpdateActiveProjectVersion{
ID: version.ID,
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
})
t.Run("Found", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
err := client.UpdateActiveProjectVersion(context.Background(), project.ID, codersdk.UpdateActiveProjectVersion{
ID: version.ID,
})
require.NoError(t, err)
})
}

View File

@ -177,13 +177,13 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty
if err != nil {
return nil, failJob(fmt.Sprintf("get workspace: %s", err))
}
projectVersion, err := server.Database.GetProjectVersionByID(ctx, workspaceBuild.ProjectVersionID)
templateVersion, err := server.Database.GetTemplateVersionByID(ctx, workspaceBuild.TemplateVersionID)
if err != nil {
return nil, failJob(fmt.Sprintf("get project version: %s", err))
return nil, failJob(fmt.Sprintf("get template version: %s", err))
}
project, err := server.Database.GetProjectByID(ctx, projectVersion.ProjectID.UUID)
template, err := server.Database.GetTemplateByID(ctx, templateVersion.TemplateID.UUID)
if err != nil {
return nil, failJob(fmt.Sprintf("get project: %s", err))
return nil, failJob(fmt.Sprintf("get template: %s", err))
}
owner, err := server.Database.GetUserByID(ctx, workspace.OwnerID)
if err != nil {
@ -192,10 +192,10 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty
// Compute parameters for the workspace to consume.
parameters, err := parameter.Compute(ctx, server.Database, parameter.ComputeScope{
ProjectImportJobID: projectVersion.JobID,
OrganizationID: job.OrganizationID,
ProjectID: uuid.NullUUID{
UUID: project.ID,
TemplateImportJobID: templateVersion.JobID,
OrganizationID: job.OrganizationID,
TemplateID: uuid.NullUUID{
UUID: template.ID,
Valid: true,
},
UserID: user.ID,
@ -235,9 +235,9 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty
},
},
}
case database.ProvisionerJobTypeProjectVersionImport:
protoJob.Type = &proto.AcquiredJob_ProjectImport_{
ProjectImport: &proto.AcquiredJob_ProjectImport{
case database.ProvisionerJobTypeTemplateVersionImport:
protoJob.Type = &proto.AcquiredJob_TemplateImport_{
TemplateImport: &proto.AcquiredJob_TemplateImport{
Metadata: &sdkproto.Provision_Metadata{
CoderUrl: server.AccessURL.String(),
},
@ -250,7 +250,7 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty
if err != nil {
return nil, failJob(fmt.Sprintf("get file by hash: %s", err))
}
protoJob.ProjectSourceArchive = file.Data
protoJob.TemplateSourceArchive = file.Data
default:
return nil, failJob(fmt.Sprintf("unsupported storage method: %s", job.StorageMethod))
}
@ -366,20 +366,20 @@ func (server *provisionerdServer) UpdateJob(ctx context.Context, request *proto.
}
}
var projectID uuid.NullUUID
if job.Type == database.ProvisionerJobTypeProjectVersionImport {
projectVersion, err := server.Database.GetProjectVersionByJobID(ctx, job.ID)
var templateID uuid.NullUUID
if job.Type == database.ProvisionerJobTypeTemplateVersionImport {
templateVersion, err := server.Database.GetTemplateVersionByJobID(ctx, job.ID)
if err != nil {
return nil, xerrors.Errorf("get project version by job id: %w", err)
return nil, xerrors.Errorf("get template version by job id: %w", err)
}
projectID = projectVersion.ProjectID
templateID = templateVersion.TemplateID
}
parameters, err := parameter.Compute(ctx, server.Database, parameter.ComputeScope{
ProjectImportJobID: job.ID,
ProjectID: projectID,
OrganizationID: job.OrganizationID,
UserID: job.InitiatorID,
TemplateImportJobID: job.ID,
TemplateID: templateID,
OrganizationID: job.OrganizationID,
UserID: job.InitiatorID,
}, nil)
if err != nil {
return nil, xerrors.Errorf("compute parameters: %w", err)
@ -450,7 +450,7 @@ func (server *provisionerdServer) FailJob(ctx context.Context, failJob *proto.Fa
if err != nil {
return nil, xerrors.Errorf("update workspace build state: %w", err)
}
case *proto.FailedJob_ProjectImport_:
case *proto.FailedJob_TemplateImport_:
}
return &proto.Empty{}, nil
}
@ -470,17 +470,17 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr
}
switch jobType := completed.Type.(type) {
case *proto.CompletedJob_ProjectImport_:
case *proto.CompletedJob_TemplateImport_:
for transition, resources := range map[database.WorkspaceTransition][]*sdkproto.Resource{
database.WorkspaceTransitionStart: jobType.ProjectImport.StartResources,
database.WorkspaceTransitionStop: jobType.ProjectImport.StopResources,
database.WorkspaceTransitionStart: jobType.TemplateImport.StartResources,
database.WorkspaceTransitionStop: jobType.TemplateImport.StopResources,
} {
addresses, err := provisionersdk.ResourceAddresses(resources)
if err != nil {
return nil, xerrors.Errorf("compute resource addresses: %w", err)
}
for index, resource := range resources {
server.Logger.Info(ctx, "inserting project import job resource",
server.Logger.Info(ctx, "inserting template import job resource",
slog.F("job_id", job.ID.String()),
slog.F("resource_name", resource.Name),
slog.F("resource_type", resource.Type),

View File

@ -21,7 +21,7 @@ func TestProvisionerJobLogs(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
@ -36,9 +36,9 @@ func TestProvisionerJobLogs(t *testing.T) {
},
}},
})
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
before := time.Now().UTC()
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
@ -59,7 +59,7 @@ func TestProvisionerJobLogs(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
@ -74,9 +74,9 @@ func TestProvisionerJobLogs(t *testing.T) {
},
}},
})
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
before := database.Now()
ctx, cancelFunc := context.WithCancel(context.Background())
t.Cleanup(cancelFunc)
@ -95,7 +95,7 @@ func TestProvisionerJobLogs(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
@ -110,9 +110,9 @@ func TestProvisionerJobLogs(t *testing.T) {
},
}},
})
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
logs, err := client.WorkspaceBuildLogsBefore(context.Background(), workspace.LatestBuild.ID, time.Now())
require.NoError(t, err)

225
coderd/templates.go Normal file
View File

@ -0,0 +1,225 @@
package coderd
import (
"database/sql"
"errors"
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/google/uuid"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/coderd/httpmw"
"github.com/coder/coder/codersdk"
)
// Returns a single template.
func (api *api) template(rw http.ResponseWriter, r *http.Request) {
template := httpmw.TemplateParam(r)
workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByTemplateIDs(r.Context(), []uuid.UUID{template.ID})
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get workspace counts: %s", err.Error()),
})
return
}
count := uint32(0)
if len(workspaceCounts) > 0 {
count = uint32(workspaceCounts[0].Count)
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, convertTemplate(template, count))
}
func (api *api) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
template := httpmw.TemplateParam(r)
workspaces, err := api.Database.GetWorkspacesByTemplateID(r.Context(), database.GetWorkspacesByTemplateIDParams{
TemplateID: template.ID,
})
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get workspaces by template id: %s", err),
})
return
}
if len(workspaces) > 0 {
httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{
Message: "All workspaces must be deleted before a template can be removed.",
})
return
}
err = api.Database.UpdateTemplateDeletedByID(r.Context(), database.UpdateTemplateDeletedByIDParams{
ID: template.ID,
Deleted: true,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("update template deleted by id: %s", err),
})
return
}
httpapi.Write(rw, http.StatusOK, httpapi.Response{
Message: "Template has been deleted!",
})
}
func (api *api) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Request) {
template := httpmw.TemplateParam(r)
versions, err := api.Database.GetTemplateVersionsByTemplateID(r.Context(), template.ID)
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get template version: %s", err),
})
return
}
jobIDs := make([]uuid.UUID, 0, len(versions))
for _, version := range versions {
jobIDs = append(jobIDs, version.JobID)
}
jobs, err := api.Database.GetProvisionerJobsByIDs(r.Context(), jobIDs)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get jobs: %s", err),
})
return
}
jobByID := map[string]database.ProvisionerJob{}
for _, job := range jobs {
jobByID[job.ID.String()] = job
}
apiVersion := make([]codersdk.TemplateVersion, 0)
for _, version := range versions {
job, exists := jobByID[version.JobID.String()]
if !exists {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("job %q doesn't exist for version %q", version.JobID, version.ID),
})
return
}
apiVersion = append(apiVersion, convertTemplateVersion(version, convertProvisionerJob(job)))
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, apiVersion)
}
func (api *api) templateVersionByName(rw http.ResponseWriter, r *http.Request) {
template := httpmw.TemplateParam(r)
templateVersionName := chi.URLParam(r, "templateversionname")
templateVersion, err := api.Database.GetTemplateVersionByTemplateIDAndName(r.Context(), database.GetTemplateVersionByTemplateIDAndNameParams{
TemplateID: uuid.NullUUID{
UUID: template.ID,
Valid: true,
},
Name: templateVersionName,
})
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
Message: fmt.Sprintf("no template version found by name %q", templateVersionName),
})
return
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get template version by name: %s", err),
})
return
}
job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get provisioner job: %s", err),
})
return
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, convertTemplateVersion(templateVersion, convertProvisionerJob(job)))
}
func (api *api) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Request) {
var req codersdk.UpdateActiveTemplateVersion
if !httpapi.Read(rw, r, &req) {
return
}
template := httpmw.TemplateParam(r)
version, err := api.Database.GetTemplateVersionByID(r.Context(), req.ID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
Message: "template version not found",
})
return
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get template version: %s", err),
})
return
}
if version.TemplateID.UUID.String() != template.ID.String() {
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
Message: "The provided template version doesn't belong to the specified template.",
})
return
}
err = api.Database.UpdateTemplateActiveVersionByID(r.Context(), database.UpdateTemplateActiveVersionByIDParams{
ID: template.ID,
ActiveVersionID: req.ID,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("update active template version: %s", err),
})
return
}
httpapi.Write(rw, http.StatusOK, httpapi.Response{
Message: "Updated the active template version!",
})
}
func convertTemplates(templates []database.Template, workspaceCounts []database.GetWorkspaceOwnerCountsByTemplateIDsRow) []codersdk.Template {
apiTemplates := make([]codersdk.Template, 0, len(templates))
for _, template := range templates {
found := false
for _, workspaceCount := range workspaceCounts {
if workspaceCount.TemplateID.String() != template.ID.String() {
continue
}
apiTemplates = append(apiTemplates, convertTemplate(template, uint32(workspaceCount.Count)))
found = true
break
}
if !found {
apiTemplates = append(apiTemplates, convertTemplate(template, uint32(0)))
}
}
return apiTemplates
}
func convertTemplate(template database.Template, workspaceOwnerCount uint32) codersdk.Template {
return codersdk.Template{
ID: template.ID,
CreatedAt: template.CreatedAt,
UpdatedAt: template.UpdatedAt,
OrganizationID: template.OrganizationID,
Name: template.Name,
Provisioner: template.Provisioner,
ActiveVersionID: template.ActiveVersionID,
WorkspaceOwnerCount: workspaceOwnerCount,
}
}

139
coderd/templates_test.go Normal file
View File

@ -0,0 +1,139 @@
package coderd_test
import (
"context"
"net/http"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
)
func TestTemplate(t *testing.T) {
t.Parallel()
t.Run("Get", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
_, err := client.Template(context.Background(), template.ID)
require.NoError(t, err)
})
}
func TestDeleteTemplate(t *testing.T) {
t.Parallel()
t.Run("NoWorkspaces", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
err := client.DeleteTemplate(context.Background(), template.ID)
require.NoError(t, err)
})
t.Run("Workspaces", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
err := client.DeleteTemplate(context.Background(), template.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
})
}
func TestTemplateVersionsByTemplate(t *testing.T) {
t.Parallel()
t.Run("Get", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
versions, err := client.TemplateVersionsByTemplate(context.Background(), template.ID)
require.NoError(t, err)
require.Len(t, versions, 1)
})
}
func TestTemplateVersionByName(t *testing.T) {
t.Parallel()
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
_, err := client.TemplateVersionByName(context.Background(), template.ID, "nothing")
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
})
t.Run("Found", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
_, err := client.TemplateVersionByName(context.Background(), template.ID, version.Name)
require.NoError(t, err)
})
}
func TestPatchActiveTemplateVersion(t *testing.T) {
t.Parallel()
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
err := client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
ID: uuid.New(),
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
})
t.Run("DoesNotBelong", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
err := client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
ID: version.ID,
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
})
t.Run("Found", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
err := client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
ID: version.ID,
})
require.NoError(t, err)
})
}

View File

@ -15,9 +15,9 @@ import (
"github.com/coder/coder/codersdk"
)
func (api *api) projectVersion(rw http.ResponseWriter, r *http.Request) {
projectVersion := httpmw.ProjectVersionParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), projectVersion.JobID)
func (api *api) templateVersion(rw http.ResponseWriter, r *http.Request) {
templateVersion := httpmw.TemplateVersionParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get provisioner job: %s", err),
@ -25,12 +25,12 @@ func (api *api) projectVersion(rw http.ResponseWriter, r *http.Request) {
return
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, convertProjectVersion(projectVersion, convertProvisionerJob(job)))
render.JSON(rw, r, convertTemplateVersion(templateVersion, convertProvisionerJob(job)))
}
func (api *api) patchCancelProjectVersion(rw http.ResponseWriter, r *http.Request) {
projectVersion := httpmw.ProjectVersionParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), projectVersion.JobID)
func (api *api) patchCancelTemplateVersion(rw http.ResponseWriter, r *http.Request) {
templateVersion := httpmw.TemplateVersionParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get provisioner job: %s", err),
@ -67,9 +67,9 @@ func (api *api) patchCancelProjectVersion(rw http.ResponseWriter, r *http.Reques
})
}
func (api *api) projectVersionSchema(rw http.ResponseWriter, r *http.Request) {
projectVersion := httpmw.ProjectVersionParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), projectVersion.JobID)
func (api *api) templateVersionSchema(rw http.ResponseWriter, r *http.Request) {
templateVersion := httpmw.TemplateVersionParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get provisioner job: %s", err),
@ -78,7 +78,7 @@ func (api *api) projectVersionSchema(rw http.ResponseWriter, r *http.Request) {
}
if !job.CompletedAt.Valid {
httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{
Message: "Project version job hasn't completed!",
Message: "Template version job hasn't completed!",
})
return
}
@ -99,10 +99,10 @@ func (api *api) projectVersionSchema(rw http.ResponseWriter, r *http.Request) {
render.JSON(rw, r, schemas)
}
func (api *api) projectVersionParameters(rw http.ResponseWriter, r *http.Request) {
func (api *api) templateVersionParameters(rw http.ResponseWriter, r *http.Request) {
apiKey := httpmw.APIKey(r)
projectVersion := httpmw.ProjectVersionParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), projectVersion.JobID)
templateVersion := httpmw.TemplateVersionParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get provisioner job: %s", err),
@ -116,9 +116,9 @@ func (api *api) projectVersionParameters(rw http.ResponseWriter, r *http.Request
return
}
values, err := parameter.Compute(r.Context(), api.Database, parameter.ComputeScope{
ProjectImportJobID: job.ID,
OrganizationID: job.OrganizationID,
UserID: apiKey.UserID,
TemplateImportJobID: job.ID,
OrganizationID: job.OrganizationID,
UserID: apiKey.UserID,
}, &parameter.ComputeOptions{
// We *never* want to send the client secret parameter values.
HideRedisplayValues: true,
@ -136,9 +136,9 @@ func (api *api) projectVersionParameters(rw http.ResponseWriter, r *http.Request
render.JSON(rw, r, values)
}
func (api *api) projectVersionResources(rw http.ResponseWriter, r *http.Request) {
projectVersion := httpmw.ProjectVersionParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), projectVersion.JobID)
func (api *api) templateVersionResources(rw http.ResponseWriter, r *http.Request) {
templateVersion := httpmw.TemplateVersionParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get provisioner job: %s", err),
@ -148,9 +148,9 @@ func (api *api) projectVersionResources(rw http.ResponseWriter, r *http.Request)
api.provisionerJobResources(rw, r, job)
}
func (api *api) projectVersionLogs(rw http.ResponseWriter, r *http.Request) {
projectVersion := httpmw.ProjectVersionParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), projectVersion.JobID)
func (api *api) templateVersionLogs(rw http.ResponseWriter, r *http.Request) {
templateVersion := httpmw.TemplateVersionParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get provisioner job: %s", err),
@ -160,13 +160,13 @@ func (api *api) projectVersionLogs(rw http.ResponseWriter, r *http.Request) {
api.provisionerJobLogs(rw, r, job)
}
func convertProjectVersion(version database.ProjectVersion, job codersdk.ProvisionerJob) codersdk.ProjectVersion {
return codersdk.ProjectVersion{
ID: version.ID,
ProjectID: &version.ProjectID.UUID,
CreatedAt: version.CreatedAt,
UpdatedAt: version.UpdatedAt,
Name: version.Name,
Job: job,
func convertTemplateVersion(version database.TemplateVersion, job codersdk.ProvisionerJob) codersdk.TemplateVersion {
return codersdk.TemplateVersion{
ID: version.ID,
TemplateID: &version.TemplateID.UUID,
CreatedAt: version.CreatedAt,
UpdatedAt: version.UpdatedAt,
Name: version.Name,
Job: job,
}
}

View File

@ -15,28 +15,28 @@ import (
"github.com/coder/coder/provisionersdk/proto"
)
func TestProjectVersion(t *testing.T) {
func TestTemplateVersion(t *testing.T) {
t.Parallel()
t.Run("Get", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
_, err := client.ProjectVersion(context.Background(), version.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_, err := client.TemplateVersion(context.Background(), version.ID)
require.NoError(t, err)
})
}
func TestPatchCancelProjectVersion(t *testing.T) {
func TestPatchCancelTemplateVersion(t *testing.T) {
t.Parallel()
t.Run("AlreadyCompleted", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
err := client.CancelProjectVersion(context.Background(), version.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
err := client.CancelTemplateVersion(context.Background(), version.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
@ -46,7 +46,7 @@ func TestPatchCancelProjectVersion(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
@ -56,14 +56,14 @@ func TestPatchCancelProjectVersion(t *testing.T) {
})
require.Eventually(t, func() bool {
var err error
version, err = client.ProjectVersion(context.Background(), version.ID)
version, err = client.TemplateVersion(context.Background(), version.ID)
require.NoError(t, err)
t.Logf("Status: %s", version.Job.Status)
return version.Job.Status == codersdk.ProvisionerJobRunning
}, 5*time.Second, 25*time.Millisecond)
err := client.CancelProjectVersion(context.Background(), version.ID)
err := client.CancelTemplateVersion(context.Background(), version.ID)
require.NoError(t, err)
err = client.CancelProjectVersion(context.Background(), version.ID)
err = client.CancelTemplateVersion(context.Background(), version.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
@ -73,7 +73,7 @@ func TestPatchCancelProjectVersion(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
@ -83,30 +83,30 @@ func TestPatchCancelProjectVersion(t *testing.T) {
})
require.Eventually(t, func() bool {
var err error
version, err = client.ProjectVersion(context.Background(), version.ID)
version, err = client.TemplateVersion(context.Background(), version.ID)
require.NoError(t, err)
t.Logf("Status: %s", version.Job.Status)
return version.Job.Status == codersdk.ProvisionerJobRunning
}, 5*time.Second, 25*time.Millisecond)
err := client.CancelProjectVersion(context.Background(), version.ID)
err := client.CancelTemplateVersion(context.Background(), version.ID)
require.NoError(t, err)
require.Eventually(t, func() bool {
var err error
version, err = client.ProjectVersion(context.Background(), version.ID)
version, err = client.TemplateVersion(context.Background(), version.ID)
require.NoError(t, err)
return version.Job.Status == codersdk.ProvisionerJobCanceled
}, 5*time.Second, 25*time.Millisecond)
})
}
func TestProjectVersionSchema(t *testing.T) {
func TestTemplateVersionSchema(t *testing.T) {
t.Parallel()
t.Run("ListRunning", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
_, err := client.ProjectVersionSchema(context.Background(), version.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_, err := client.TemplateVersionSchema(context.Background(), version.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
@ -116,7 +116,7 @@ func TestProjectVersionSchema(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: []*proto.Parse_Response{{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
@ -131,22 +131,22 @@ func TestProjectVersionSchema(t *testing.T) {
}},
Provision: echo.ProvisionComplete,
})
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
schemas, err := client.ProjectVersionSchema(context.Background(), version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
schemas, err := client.TemplateVersionSchema(context.Background(), version.ID)
require.NoError(t, err)
require.NotNil(t, schemas)
require.Len(t, schemas, 1)
})
}
func TestProjectVersionParameters(t *testing.T) {
func TestTemplateVersionParameters(t *testing.T) {
t.Parallel()
t.Run("ListRunning", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
_, err := client.ProjectVersionParameters(context.Background(), version.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_, err := client.TemplateVersionParameters(context.Background(), version.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
@ -156,7 +156,7 @@ func TestProjectVersionParameters(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: []*proto.Parse_Response{{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
@ -176,8 +176,8 @@ func TestProjectVersionParameters(t *testing.T) {
}},
Provision: echo.ProvisionComplete,
})
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
params, err := client.ProjectVersionParameters(context.Background(), version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
params, err := client.TemplateVersionParameters(context.Background(), version.ID)
require.NoError(t, err)
require.NotNil(t, params)
require.Len(t, params, 1)
@ -185,14 +185,14 @@ func TestProjectVersionParameters(t *testing.T) {
})
}
func TestProjectVersionResources(t *testing.T) {
func TestTemplateVersionResources(t *testing.T) {
t.Parallel()
t.Run("ListRunning", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
_, err := client.ProjectVersionResources(context.Background(), version.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_, err := client.TemplateVersionResources(context.Background(), version.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
@ -202,7 +202,7 @@ func TestProjectVersionResources(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
@ -222,8 +222,8 @@ func TestProjectVersionResources(t *testing.T) {
},
}},
})
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
resources, err := client.ProjectVersionResources(context.Background(), version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
resources, err := client.TemplateVersionResources(context.Background(), version.ID)
require.NoError(t, err)
require.NotNil(t, resources)
require.Len(t, resources, 4)
@ -233,13 +233,13 @@ func TestProjectVersionResources(t *testing.T) {
})
}
func TestProjectVersionLogs(t *testing.T) {
func TestTemplateVersionLogs(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
before := time.Now()
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionDryRun: echo.ProvisionComplete,
Provision: []*proto.Provision_Response{{
@ -271,7 +271,7 @@ func TestProjectVersionLogs(t *testing.T) {
})
ctx, cancelFunc := context.WithCancel(context.Background())
t.Cleanup(cancelFunc)
logs, err := client.ProjectVersionLogsAfter(ctx, version.ID, before)
logs, err := client.TemplateVersionLogsAfter(ctx, version.ID, before)
require.NoError(t, err)
for {
_, ok := <-logs

View File

@ -23,7 +23,7 @@ const (
// Compare checks the equality of passwords from a hashed pbkdf2 string.
// This uses pbkdf2 to ensure FIPS 140-2 compliance. See:
// https://csrc.nist.gov/csrc/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp2261.pdf
// https://csrc.nist.gov/csrc/media/templates/cryptographic-module-validation-program/documents/security-policies/140sp2261.pdf
func Compare(hashed string, password string) (bool, error) {
if len(hashed) < hashLength {
return false, xerrors.Errorf("hash too short: %d", len(hashed))

View File

@ -529,12 +529,12 @@ func (api *api) postWorkspacesByUser(rw http.ResponseWriter, r *http.Request) {
return
}
apiKey := httpmw.APIKey(r)
project, err := api.Database.GetProjectByID(r.Context(), createWorkspace.ProjectID)
template, err := api.Database.GetTemplateByID(r.Context(), createWorkspace.TemplateID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: fmt.Sprintf("project %q doesn't exist", createWorkspace.ProjectID.String()),
Message: fmt.Sprintf("template %q doesn't exist", createWorkspace.TemplateID.String()),
Errors: []httpapi.Error{{
Field: "project_id",
Field: "template_id",
Code: "not_found",
}},
})
@ -542,17 +542,17 @@ func (api *api) postWorkspacesByUser(rw http.ResponseWriter, r *http.Request) {
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project: %s", err),
Message: fmt.Sprintf("get template: %s", err),
})
return
}
_, err = api.Database.GetOrganizationMemberByUserID(r.Context(), database.GetOrganizationMemberByUserIDParams{
OrganizationID: project.OrganizationID,
OrganizationID: template.OrganizationID,
UserID: apiKey.UserID,
})
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
Message: "you aren't allowed to access projects in that organization",
Message: "you aren't allowed to access templates in that organization",
})
return
}
@ -569,16 +569,16 @@ func (api *api) postWorkspacesByUser(rw http.ResponseWriter, r *http.Request) {
})
if err == nil {
// If the workspace already exists, don't allow creation.
project, err := api.Database.GetProjectByID(r.Context(), workspace.ProjectID)
template, err := api.Database.GetTemplateByID(r.Context(), workspace.TemplateID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("find project for conflicting workspace name %q: %s", createWorkspace.Name, err),
Message: fmt.Sprintf("find template for conflicting workspace name %q: %s", createWorkspace.Name, err),
})
return
}
// The project is fetched for clarity to the user on where the conflicting name may be.
// The template is fetched for clarity to the user on where the conflicting name may be.
httpapi.Write(rw, http.StatusConflict, httpapi.Response{
Message: fmt.Sprintf("workspace %q already exists in the %q project", createWorkspace.Name, project.Name),
Message: fmt.Sprintf("workspace %q already exists in the %q template", createWorkspace.Name, template.Name),
Errors: []httpapi.Error{{
Field: "name",
Code: "exists",
@ -593,35 +593,35 @@ func (api *api) postWorkspacesByUser(rw http.ResponseWriter, r *http.Request) {
return
}
projectVersion, err := api.Database.GetProjectVersionByID(r.Context(), project.ActiveVersionID)
templateVersion, err := api.Database.GetTemplateVersionByID(r.Context(), template.ActiveVersionID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project version: %s", err),
Message: fmt.Sprintf("get template version: %s", err),
})
return
}
projectVersionJob, err := api.Database.GetProvisionerJobByID(r.Context(), projectVersion.JobID)
templateVersionJob, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project version job: %s", err),
Message: fmt.Sprintf("get template version job: %s", err),
})
return
}
projectVersionJobStatus := convertProvisionerJob(projectVersionJob).Status
switch projectVersionJobStatus {
templateVersionJobStatus := convertProvisionerJob(templateVersionJob).Status
switch templateVersionJobStatus {
case codersdk.ProvisionerJobPending, codersdk.ProvisionerJobRunning:
httpapi.Write(rw, http.StatusNotAcceptable, httpapi.Response{
Message: fmt.Sprintf("The provided project version is %s. Wait for it to complete importing!", projectVersionJobStatus),
Message: fmt.Sprintf("The provided template version is %s. Wait for it to complete importing!", templateVersionJobStatus),
})
return
case codersdk.ProvisionerJobFailed:
httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{
Message: fmt.Sprintf("The provided project version %q has failed to import. You cannot create workspaces using it!", projectVersion.Name),
Message: fmt.Sprintf("The provided template version %q has failed to import. You cannot create workspaces using it!", templateVersion.Name),
})
return
case codersdk.ProvisionerJobCanceled:
httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{
Message: "The provided project version was canceled during import. You cannot create workspaces using it!",
Message: "The provided template version was canceled during import. You cannot create workspaces using it!",
})
return
}
@ -632,12 +632,12 @@ func (api *api) postWorkspacesByUser(rw http.ResponseWriter, r *http.Request) {
workspaceBuildID := uuid.New()
// Workspaces are created without any versions.
workspace, err = db.InsertWorkspace(r.Context(), database.InsertWorkspaceParams{
ID: uuid.New(),
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
OwnerID: apiKey.UserID,
ProjectID: project.ID,
Name: createWorkspace.Name,
ID: uuid.New(),
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
OwnerID: apiKey.UserID,
TemplateID: template.ID,
Name: createWorkspace.Name,
})
if err != nil {
return xerrors.Errorf("insert workspace: %w", err)
@ -670,26 +670,26 @@ func (api *api) postWorkspacesByUser(rw http.ResponseWriter, r *http.Request) {
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
InitiatorID: apiKey.UserID,
OrganizationID: project.OrganizationID,
Provisioner: project.Provisioner,
OrganizationID: template.OrganizationID,
Provisioner: template.Provisioner,
Type: database.ProvisionerJobTypeWorkspaceBuild,
StorageMethod: projectVersionJob.StorageMethod,
StorageSource: projectVersionJob.StorageSource,
StorageMethod: templateVersionJob.StorageMethod,
StorageSource: templateVersionJob.StorageSource,
Input: input,
})
if err != nil {
return xerrors.Errorf("insert provisioner job: %w", err)
}
workspaceBuild, err = db.InsertWorkspaceBuild(r.Context(), database.InsertWorkspaceBuildParams{
ID: workspaceBuildID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
WorkspaceID: workspace.ID,
ProjectVersionID: projectVersion.ID,
Name: namesgenerator.GetRandomName(1),
InitiatorID: apiKey.UserID,
Transition: database.WorkspaceTransitionStart,
JobID: provisionerJob.ID,
ID: workspaceBuildID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
WorkspaceID: workspace.ID,
TemplateVersionID: templateVersion.ID,
Name: namesgenerator.GetRandomName(1),
InitiatorID: apiKey.UserID,
Transition: database.WorkspaceTransitionStart,
JobID: provisionerJob.ID,
})
if err != nil {
return xerrors.Errorf("insert workspace build: %w", err)
@ -705,7 +705,7 @@ func (api *api) postWorkspacesByUser(rw http.ResponseWriter, r *http.Request) {
render.Status(r, http.StatusCreated)
render.JSON(rw, r, convertWorkspace(workspace,
convertWorkspaceBuild(workspaceBuild, convertProvisionerJob(projectVersionJob)), project))
convertWorkspaceBuild(workspaceBuild, convertProvisionerJob(templateVersionJob)), template))
}
func (api *api) workspacesByUser(rw http.ResponseWriter, r *http.Request) {
@ -723,10 +723,10 @@ func (api *api) workspacesByUser(rw http.ResponseWriter, r *http.Request) {
return
}
workspaceIDs := make([]uuid.UUID, 0, len(workspaces))
projectIDs := make([]uuid.UUID, 0, len(workspaces))
templateIDs := make([]uuid.UUID, 0, len(workspaces))
for _, workspace := range workspaces {
workspaceIDs = append(workspaceIDs, workspace.ID)
projectIDs = append(projectIDs, workspace.ProjectID)
templateIDs = append(templateIDs, workspace.TemplateID)
}
workspaceBuilds, err := api.Database.GetWorkspaceBuildsByWorkspaceIDsWithoutAfter(r.Context(), workspaceIDs)
if errors.Is(err, sql.ErrNoRows) {
@ -738,13 +738,13 @@ func (api *api) workspacesByUser(rw http.ResponseWriter, r *http.Request) {
})
return
}
projects, err := api.Database.GetProjectsByIDs(r.Context(), projectIDs)
templates, err := api.Database.GetTemplatesByIDs(r.Context(), templateIDs)
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get projects: %s", err),
Message: fmt.Sprintf("get templates: %s", err),
})
return
}
@ -767,9 +767,9 @@ func (api *api) workspacesByUser(rw http.ResponseWriter, r *http.Request) {
for _, workspaceBuild := range workspaceBuilds {
buildByWorkspaceID[workspaceBuild.WorkspaceID] = workspaceBuild
}
projectByID := map[uuid.UUID]database.Project{}
for _, project := range projects {
projectByID[project.ID] = project
templateByID := map[uuid.UUID]database.Template{}
for _, template := range templates {
templateByID[template.ID] = template
}
jobByID := map[uuid.UUID]database.ProvisionerJob{}
for _, job := range jobs {
@ -784,10 +784,10 @@ func (api *api) workspacesByUser(rw http.ResponseWriter, r *http.Request) {
})
return
}
project, exists := projectByID[workspace.ProjectID]
template, exists := templateByID[workspace.TemplateID]
if !exists {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("project not found for workspace %q", workspace.Name),
Message: fmt.Sprintf("template not found for workspace %q", workspace.Name),
})
return
}
@ -799,7 +799,7 @@ func (api *api) workspacesByUser(rw http.ResponseWriter, r *http.Request) {
return
}
apiWorkspaces = append(apiWorkspaces,
convertWorkspace(workspace, convertWorkspaceBuild(build, convertProvisionerJob(job)), project))
convertWorkspace(workspace, convertWorkspaceBuild(build, convertProvisionerJob(job)), template))
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, apiWorkspaces)
@ -838,17 +838,17 @@ func (api *api) workspaceByUserAndName(rw http.ResponseWriter, r *http.Request)
})
return
}
project, err := api.Database.GetProjectByID(r.Context(), workspace.ProjectID)
template, err := api.Database.GetTemplateByID(r.Context(), workspace.TemplateID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project: %s", err),
Message: fmt.Sprintf("get template: %s", err),
})
return
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, convertWorkspace(workspace,
convertWorkspaceBuild(build, convertProvisionerJob(job)), project))
convertWorkspaceBuild(build, convertProvisionerJob(job)), template))
}
// Generates a new ID and secret for an API key.

View File

@ -310,13 +310,13 @@ func TestPostAPIKey(t *testing.T) {
func TestPostWorkspacesByUser(t *testing.T) {
t.Parallel()
t.Run("InvalidProject", func(t *testing.T) {
t.Run("InvalidTemplate", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, client)
_, err := client.CreateWorkspace(context.Background(), codersdk.Me, codersdk.CreateWorkspaceRequest{
ProjectID: uuid.New(),
Name: "workspace",
TemplateID: uuid.New(),
Name: "workspace",
})
require.Error(t, err)
var apiErr *codersdk.Error
@ -324,7 +324,7 @@ func TestPostWorkspacesByUser(t *testing.T) {
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
})
t.Run("NoProjectAccess", func(t *testing.T) {
t.Run("NoTemplateAccess", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
first := coderdtest.CreateFirstUser(t, client)
@ -334,12 +334,12 @@ func TestPostWorkspacesByUser(t *testing.T) {
Name: "another",
})
require.NoError(t, err)
version := coderdtest.CreateProjectVersion(t, other, org.ID, nil)
project := coderdtest.CreateProject(t, other, org.ID, version.ID)
version := coderdtest.CreateTemplateVersion(t, other, org.ID, nil)
template := coderdtest.CreateTemplate(t, other, org.ID, version.ID)
_, err = client.CreateWorkspace(context.Background(), codersdk.Me, codersdk.CreateWorkspaceRequest{
ProjectID: project.ID,
Name: "workspace",
TemplateID: template.ID,
Name: "workspace",
})
require.Error(t, err)
var apiErr *codersdk.Error
@ -352,13 +352,13 @@ func TestPostWorkspacesByUser(t *testing.T) {
client := coderdtest.New(t, nil)
coderdtest.NewProvisionerDaemon(t, client)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
_, err := client.CreateWorkspace(context.Background(), codersdk.Me, codersdk.CreateWorkspaceRequest{
ProjectID: project.ID,
Name: workspace.Name,
TemplateID: template.ID,
Name: workspace.Name,
})
require.Error(t, err)
var apiErr *codersdk.Error
@ -371,10 +371,10 @@ func TestPostWorkspacesByUser(t *testing.T) {
client := coderdtest.New(t, nil)
coderdtest.NewProvisionerDaemon(t, client)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
_ = coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
_ = coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
})
}
@ -392,10 +392,10 @@ func TestWorkspacesByUser(t *testing.T) {
client := coderdtest.New(t, nil)
coderdtest.NewProvisionerDaemon(t, client)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
_ = coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
_ = coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
workspaces, err := client.WorkspacesByUser(context.Background(), codersdk.Me)
require.NoError(t, err)
require.Len(t, workspaces, 1)
@ -418,10 +418,10 @@ func TestWorkspaceByUserAndName(t *testing.T) {
client := coderdtest.New(t, nil)
coderdtest.NewProvisionerDaemon(t, client)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
_, err := client.WorkspaceByName(context.Background(), codersdk.Me, workspace.Name)
require.NoError(t, err)
})

View File

@ -92,17 +92,17 @@ func (api *api) workspaceBuildLogs(rw http.ResponseWriter, r *http.Request) {
func convertWorkspaceBuild(workspaceBuild database.WorkspaceBuild, job codersdk.ProvisionerJob) codersdk.WorkspaceBuild {
//nolint:unconvert
return codersdk.WorkspaceBuild{
ID: workspaceBuild.ID,
CreatedAt: workspaceBuild.CreatedAt,
UpdatedAt: workspaceBuild.UpdatedAt,
WorkspaceID: workspaceBuild.WorkspaceID,
ProjectVersionID: workspaceBuild.ProjectVersionID,
BeforeID: workspaceBuild.BeforeID.UUID,
AfterID: workspaceBuild.AfterID.UUID,
Name: workspaceBuild.Name,
Transition: workspaceBuild.Transition,
InitiatorID: workspaceBuild.InitiatorID,
Job: job,
ID: workspaceBuild.ID,
CreatedAt: workspaceBuild.CreatedAt,
UpdatedAt: workspaceBuild.UpdatedAt,
WorkspaceID: workspaceBuild.WorkspaceID,
TemplateVersionID: workspaceBuild.TemplateVersionID,
BeforeID: workspaceBuild.BeforeID.UUID,
AfterID: workspaceBuild.AfterID.UUID,
Name: workspaceBuild.Name,
Transition: workspaceBuild.Transition,
InitiatorID: workspaceBuild.InitiatorID,
Job: job,
}
}

View File

@ -19,10 +19,10 @@ func TestWorkspaceBuild(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
_, err := client.WorkspaceBuild(context.Background(), workspace.LatestBuild.ID)
require.NoError(t, err)
}
@ -32,7 +32,7 @@ func TestPatchCancelWorkspaceBuild(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
@ -41,9 +41,9 @@ func TestPatchCancelWorkspaceBuild(t *testing.T) {
}},
ProvisionDryRun: echo.ProvisionComplete,
})
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
var build codersdk.WorkspaceBuild
require.Eventually(t, func() bool {
var err error
@ -68,11 +68,11 @@ func TestWorkspaceBuildResources(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
closeDaemon := coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
closeDaemon.Close()
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
_, err := client.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
@ -83,7 +83,7 @@ func TestWorkspaceBuildResources(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
@ -103,9 +103,9 @@ func TestWorkspaceBuildResources(t *testing.T) {
},
}},
})
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
resources, err := client.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID)
require.NoError(t, err)
@ -123,7 +123,7 @@ func TestWorkspaceBuildLogs(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
before := time.Now()
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
@ -150,9 +150,9 @@ func TestWorkspaceBuildLogs(t *testing.T) {
},
}},
})
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
ctx, cancelFunc := context.WithCancel(context.Background())
t.Cleanup(cancelFunc)
logs, err := client.WorkspaceBuildLogsAfter(ctx, workspace.LatestBuild.ID, before)

View File

@ -24,7 +24,7 @@ func TestPostWorkspaceAuthAWSInstanceIdentity(t *testing.T) {
})
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
@ -42,9 +42,9 @@ func TestPostWorkspaceAuthAWSInstanceIdentity(t *testing.T) {
},
}},
})
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
client.HTTPClient = metadataClient
@ -90,7 +90,7 @@ func TestPostWorkspaceAuthGoogleInstanceIdentity(t *testing.T) {
})
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
@ -108,9 +108,9 @@ func TestPostWorkspaceAuthGoogleInstanceIdentity(t *testing.T) {
},
}},
})
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
_, err := client.AuthWorkspaceGoogleInstanceIdentity(context.Background(), "", metadata)

View File

@ -25,7 +25,7 @@ func TestWorkspaceResource(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
@ -42,9 +42,9 @@ func TestWorkspaceResource(t *testing.T) {
},
}},
})
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
resources, err := client.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID)
require.NoError(t, err)
@ -59,7 +59,7 @@ func TestWorkspaceAgentListen(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client)
daemonCloser := coderdtest.NewProvisionerDaemon(t, client)
authToken := uuid.NewString()
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionDryRun: echo.ProvisionComplete,
Provision: []*proto.Provision_Response{{
@ -79,9 +79,9 @@ func TestWorkspaceAgentListen(t *testing.T) {
},
}},
})
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
daemonCloser.Close()

View File

@ -37,14 +37,14 @@ func (api *api) workspace(rw http.ResponseWriter, r *http.Request) {
})
return
}
project, err := api.Database.GetProjectByID(r.Context(), workspace.ProjectID)
template, err := api.Database.GetTemplateByID(r.Context(), workspace.TemplateID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project: %s", err),
Message: fmt.Sprintf("get template: %s", err),
})
return
}
render.JSON(rw, r, convertWorkspace(workspace, convertWorkspaceBuild(build, convertProvisionerJob(job)), project))
render.JSON(rw, r, convertWorkspace(workspace, convertWorkspaceBuild(build, convertProvisionerJob(job)), template))
}
func (api *api) workspaceBuilds(rw http.ResponseWriter, r *http.Request) {
@ -102,7 +102,7 @@ func (api *api) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
if !httpapi.Read(rw, r, &createBuild) {
return
}
if createBuild.ProjectVersionID == uuid.Nil {
if createBuild.TemplateVersionID == uuid.Nil {
latestBuild, err := api.Database.GetWorkspaceBuildByWorkspaceIDWithoutAfter(r.Context(), workspace.ID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
@ -110,14 +110,14 @@ func (api *api) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
})
return
}
createBuild.ProjectVersionID = latestBuild.ProjectVersionID
createBuild.TemplateVersionID = latestBuild.TemplateVersionID
}
projectVersion, err := api.Database.GetProjectVersionByID(r.Context(), createBuild.ProjectVersionID)
templateVersion, err := api.Database.GetTemplateVersionByID(r.Context(), createBuild.TemplateVersionID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: "project version not found",
Message: "template version not found",
Errors: []httpapi.Error{{
Field: "project_version_id",
Field: "template_version_id",
Code: "exists",
}},
})
@ -125,40 +125,40 @@ func (api *api) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project version: %s", err),
Message: fmt.Sprintf("get template version: %s", err),
})
return
}
projectVersionJob, err := api.Database.GetProvisionerJobByID(r.Context(), projectVersion.JobID)
templateVersionJob, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get provisioner job: %s", err),
})
return
}
projectVersionJobStatus := convertProvisionerJob(projectVersionJob).Status
switch projectVersionJobStatus {
templateVersionJobStatus := convertProvisionerJob(templateVersionJob).Status
switch templateVersionJobStatus {
case codersdk.ProvisionerJobPending, codersdk.ProvisionerJobRunning:
httpapi.Write(rw, http.StatusNotAcceptable, httpapi.Response{
Message: fmt.Sprintf("The provided project version is %s. Wait for it to complete importing!", projectVersionJobStatus),
Message: fmt.Sprintf("The provided template version is %s. Wait for it to complete importing!", templateVersionJobStatus),
})
return
case codersdk.ProvisionerJobFailed:
httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{
Message: fmt.Sprintf("The provided project version %q has failed to import: %q. You cannot build workspaces with it!", projectVersion.Name, projectVersionJob.Error.String),
Message: fmt.Sprintf("The provided template version %q has failed to import: %q. You cannot build workspaces with it!", templateVersion.Name, templateVersionJob.Error.String),
})
return
case codersdk.ProvisionerJobCanceled:
httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{
Message: "The provided project version was canceled during import. You cannot builds workspaces with it!",
Message: "The provided template version was canceled during import. You cannot builds workspaces with it!",
})
return
}
project, err := api.Database.GetProjectByID(r.Context(), projectVersion.ProjectID.UUID)
template, err := api.Database.GetTemplateByID(r.Context(), templateVersion.TemplateID.UUID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project: %s", err),
Message: fmt.Sprintf("get template: %s", err),
})
return
}
@ -203,28 +203,28 @@ func (api *api) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
InitiatorID: apiKey.UserID,
OrganizationID: project.OrganizationID,
Provisioner: project.Provisioner,
OrganizationID: template.OrganizationID,
Provisioner: template.Provisioner,
Type: database.ProvisionerJobTypeWorkspaceBuild,
StorageMethod: projectVersionJob.StorageMethod,
StorageSource: projectVersionJob.StorageSource,
StorageMethod: templateVersionJob.StorageMethod,
StorageSource: templateVersionJob.StorageSource,
Input: input,
})
if err != nil {
return xerrors.Errorf("insert provisioner job: %w", err)
}
workspaceBuild, err = db.InsertWorkspaceBuild(r.Context(), database.InsertWorkspaceBuildParams{
ID: workspaceBuildID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
WorkspaceID: workspace.ID,
ProjectVersionID: projectVersion.ID,
BeforeID: priorHistoryID,
Name: namesgenerator.GetRandomName(1),
ProvisionerState: priorHistory.ProvisionerState,
InitiatorID: apiKey.UserID,
Transition: createBuild.Transition,
JobID: provisionerJob.ID,
ID: workspaceBuildID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
WorkspaceID: workspace.ID,
TemplateVersionID: templateVersion.ID,
BeforeID: priorHistoryID,
Name: namesgenerator.GetRandomName(1),
ProvisionerState: priorHistory.ProvisionerState,
InitiatorID: apiKey.UserID,
Transition: createBuild.Transition,
JobID: provisionerJob.ID,
})
if err != nil {
return xerrors.Errorf("insert workspace build: %w", err)
@ -290,16 +290,16 @@ func (api *api) workspaceBuildByName(rw http.ResponseWriter, r *http.Request) {
render.JSON(rw, r, convertWorkspaceBuild(workspaceBuild, convertProvisionerJob(job)))
}
func convertWorkspace(workspace database.Workspace, workspaceBuild codersdk.WorkspaceBuild, project database.Project) codersdk.Workspace {
func convertWorkspace(workspace database.Workspace, workspaceBuild codersdk.WorkspaceBuild, template database.Template) codersdk.Workspace {
return codersdk.Workspace{
ID: workspace.ID,
CreatedAt: workspace.CreatedAt,
UpdatedAt: workspace.UpdatedAt,
OwnerID: workspace.OwnerID,
ProjectID: workspace.ProjectID,
LatestBuild: workspaceBuild,
ProjectName: project.Name,
Outdated: workspaceBuild.ProjectVersionID.String() != project.ActiveVersionID.String(),
Name: workspace.Name,
ID: workspace.ID,
CreatedAt: workspace.CreatedAt,
UpdatedAt: workspace.UpdatedAt,
OwnerID: workspace.OwnerID,
TemplateID: workspace.TemplateID,
LatestBuild: workspaceBuild,
TemplateName: template.Name,
Outdated: workspaceBuild.TemplateVersionID.String() != template.ActiveVersionID.String(),
Name: workspace.Name,
}
}

View File

@ -20,10 +20,10 @@ func TestWorkspace(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
_, err := client.Workspace(context.Background(), workspace.ID)
require.NoError(t, err)
}
@ -35,10 +35,10 @@ func TestWorkspaceBuilds(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
_, err := client.WorkspaceBuilds(context.Background(), workspace.ID)
require.NoError(t, err)
})
@ -46,18 +46,18 @@ func TestWorkspaceBuilds(t *testing.T) {
func TestPostWorkspaceBuild(t *testing.T) {
t.Parallel()
t.Run("NoProjectVersion", func(t *testing.T) {
t.Run("NoTemplateVersion", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.NewProvisionerDaemon(t, client)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
_, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
ProjectVersionID: uuid.New(),
Transition: database.WorkspaceTransitionStart,
TemplateVersionID: uuid.New(),
Transition: database.WorkspaceTransitionStart,
})
require.Error(t, err)
var apiErr *codersdk.Error
@ -65,19 +65,19 @@ func TestPostWorkspaceBuild(t *testing.T) {
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
})
t.Run("ProjectVersionFailedImport", func(t *testing.T) {
t.Run("TemplateVersionFailedImport", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Provision: []*proto.Provision_Response{{}},
})
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
_, err := client.CreateWorkspace(context.Background(), codersdk.Me, codersdk.CreateWorkspaceRequest{
ProjectID: project.ID,
Name: "workspace",
TemplateID: template.ID,
Name: "workspace",
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
@ -89,15 +89,15 @@ func TestPostWorkspaceBuild(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
closeDaemon := coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
// Close here so workspace build doesn't process!
closeDaemon.Close()
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
_, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
TemplateVersionID: template.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.Error(t, err)
var apiErr *codersdk.Error
@ -110,14 +110,14 @@ func TestPostWorkspaceBuild(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
build, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
TemplateVersionID: template.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.NoError(t, err)
require.Equal(t, workspace.LatestBuild.ID.String(), build.BeforeID.String())
@ -132,10 +132,10 @@ func TestPostWorkspaceBuild(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
build, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
Transition: database.WorkspaceTransitionDelete,
@ -157,10 +157,10 @@ func TestWorkspaceBuildByName(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
_, err := client.WorkspaceBuildByName(context.Background(), workspace.ID, "something")
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
@ -172,10 +172,10 @@ func TestWorkspaceBuildByName(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, template.ID)
build, err := client.WorkspaceBuild(context.Background(), workspace.LatestBuild.ID)
require.NoError(t, err)
_, err = client.WorkspaceBuildByName(context.Background(), workspace.ID, build.Name)

View File

@ -21,10 +21,10 @@ type Organization struct {
UpdatedAt time.Time `json:"updated_at" validate:"required"`
}
// CreateProjectVersionRequest enables callers to create a new Project Version.
type CreateProjectVersionRequest struct {
// ProjectID optionally associates a version with a project.
ProjectID uuid.UUID `json:"project_id"`
// CreateTemplateVersionRequest enables callers to create a new Template Version.
type CreateTemplateVersionRequest struct {
// TemplateID optionally associates a version with a template.
TemplateID uuid.UUID `json:"template_id"`
StorageMethod database.ProvisionerStorageMethod `json:"storage_method" validate:"oneof=file,required"`
StorageSource string `json:"storage_source" validate:"required"`
@ -34,17 +34,17 @@ type CreateProjectVersionRequest struct {
ParameterValues []CreateParameterRequest `json:"parameter_values"`
}
// CreateProjectRequest provides options when creating a project.
type CreateProjectRequest struct {
// CreateTemplateRequest provides options when creating a template.
type CreateTemplateRequest struct {
Name string `json:"name" validate:"username,required"`
// VersionID is an in-progress or completed job to use as
// an initial version of the project.
// an initial version of the template.
//
// This is required on creation to enable a user-flow of validating a
// project works. There is no reason the data-model cannot support
// empty projects, but it doesn't make sense for users.
VersionID uuid.UUID `json:"project_version_id" validate:"required"`
// template works. There is no reason the data-model cannot support
// empty templates, but it doesn't make sense for users.
VersionID uuid.UUID `json:"template_version_id" validate:"required"`
ParameterValues []CreateParameterRequest `json:"parameter_values"`
}
@ -82,49 +82,49 @@ func (c *Client) ProvisionerDaemonsByOrganization(ctx context.Context, organizat
return daemons, json.NewDecoder(res.Body).Decode(&daemons)
}
// CreateProjectVersion processes source-code and optionally associates the version with a project.
// Executing without a project is useful for validating source-code.
func (c *Client) CreateProjectVersion(ctx context.Context, organizationID uuid.UUID, req CreateProjectVersionRequest) (ProjectVersion, error) {
// CreateTemplateVersion processes source-code and optionally associates the version with a template.
// Executing without a template is useful for validating source-code.
func (c *Client) CreateTemplateVersion(ctx context.Context, organizationID uuid.UUID, req CreateTemplateVersionRequest) (TemplateVersion, error) {
res, err := c.request(ctx, http.MethodPost,
fmt.Sprintf("/api/v2/organizations/%s/projectversions", organizationID.String()),
fmt.Sprintf("/api/v2/organizations/%s/templateversions", organizationID.String()),
req,
)
if err != nil {
return ProjectVersion{}, xerrors.Errorf("execute request: %w", err)
return TemplateVersion{}, xerrors.Errorf("execute request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return ProjectVersion{}, readBodyAsError(res)
return TemplateVersion{}, readBodyAsError(res)
}
var projectVersion ProjectVersion
return projectVersion, json.NewDecoder(res.Body).Decode(&projectVersion)
var templateVersion TemplateVersion
return templateVersion, json.NewDecoder(res.Body).Decode(&templateVersion)
}
// CreateProject creates a new project inside an organization.
func (c *Client) CreateProject(ctx context.Context, organizationID uuid.UUID, request CreateProjectRequest) (Project, error) {
// CreateTemplate creates a new template inside an organization.
func (c *Client) CreateTemplate(ctx context.Context, organizationID uuid.UUID, request CreateTemplateRequest) (Template, error) {
res, err := c.request(ctx, http.MethodPost,
fmt.Sprintf("/api/v2/organizations/%s/projects", organizationID.String()),
fmt.Sprintf("/api/v2/organizations/%s/templates", organizationID.String()),
request,
)
if err != nil {
return Project{}, xerrors.Errorf("execute request: %w", err)
return Template{}, xerrors.Errorf("execute request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return Project{}, readBodyAsError(res)
return Template{}, readBodyAsError(res)
}
var project Project
return project, json.NewDecoder(res.Body).Decode(&project)
var template Template
return template, json.NewDecoder(res.Body).Decode(&template)
}
// ProjectsByOrganization lists all projects inside of an organization.
func (c *Client) ProjectsByOrganization(ctx context.Context, organizationID uuid.UUID) ([]Project, error) {
// TemplatesByOrganization lists all templates inside of an organization.
func (c *Client) TemplatesByOrganization(ctx context.Context, organizationID uuid.UUID) ([]Template, error) {
res, err := c.request(ctx, http.MethodGet,
fmt.Sprintf("/api/v2/organizations/%s/projects", organizationID.String()),
fmt.Sprintf("/api/v2/organizations/%s/templates", organizationID.String()),
nil,
)
if err != nil {
@ -136,25 +136,25 @@ func (c *Client) ProjectsByOrganization(ctx context.Context, organizationID uuid
return nil, readBodyAsError(res)
}
var projects []Project
return projects, json.NewDecoder(res.Body).Decode(&projects)
var templates []Template
return templates, json.NewDecoder(res.Body).Decode(&templates)
}
// ProjectByName finds a project inside the organization provided with a case-insensitive name.
func (c *Client) ProjectByName(ctx context.Context, organizationID uuid.UUID, name string) (Project, error) {
// TemplateByName finds a template inside the organization provided with a case-insensitive name.
func (c *Client) TemplateByName(ctx context.Context, organizationID uuid.UUID, name string) (Template, error) {
res, err := c.request(ctx, http.MethodGet,
fmt.Sprintf("/api/v2/organizations/%s/projects/%s", organizationID.String(), name),
fmt.Sprintf("/api/v2/organizations/%s/templates/%s", organizationID.String(), name),
nil,
)
if err != nil {
return Project{}, xerrors.Errorf("execute request: %w", err)
return Template{}, xerrors.Errorf("execute request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return Project{}, readBodyAsError(res)
return Template{}, readBodyAsError(res)
}
var project Project
return project, json.NewDecoder(res.Body).Decode(&project)
var template Template
return template, json.NewDecoder(res.Body).Decode(&template)
}

View File

@ -17,7 +17,7 @@ type ParameterScope string
const (
ParameterOrganization ParameterScope = "organization"
ParameterProject ParameterScope = "project"
ParameterTemplate ParameterScope = "template"
ParameterUser ParameterScope = "user"
ParameterWorkspace ParameterScope = "workspace"
)

View File

@ -1,109 +0,0 @@
package codersdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/google/uuid"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/parameter"
)
// ProjectVersion represents a single version of a project.
type ProjectVersion struct {
ID uuid.UUID `json:"id"`
ProjectID *uuid.UUID `json:"project_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Name string `json:"name"`
Job ProvisionerJob `json:"job"`
}
// ProjectVersionParameterSchema represents a parameter parsed from project version source.
type ProjectVersionParameterSchema database.ParameterSchema
// ProjectVersionParameter represents a computed parameter value.
type ProjectVersionParameter parameter.ComputedValue
// ProjectVersion returns a project version by ID.
func (c *Client) ProjectVersion(ctx context.Context, id uuid.UUID) (ProjectVersion, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projectversions/%s", id), nil)
if err != nil {
return ProjectVersion{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return ProjectVersion{}, readBodyAsError(res)
}
var version ProjectVersion
return version, json.NewDecoder(res.Body).Decode(&version)
}
// CancelProjectVersion marks a project version job as canceled.
func (c *Client) CancelProjectVersion(ctx context.Context, version uuid.UUID) error {
res, err := c.request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/projectversions/%s/cancel", version), nil)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return readBodyAsError(res)
}
return nil
}
// ProjectVersionSchema returns schemas for a project version by ID.
func (c *Client) ProjectVersionSchema(ctx context.Context, version uuid.UUID) ([]ProjectVersionParameterSchema, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projectversions/%s/schema", version), nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var params []ProjectVersionParameterSchema
return params, json.NewDecoder(res.Body).Decode(&params)
}
// ProjectVersionParameters returns computed parameters for a project version.
func (c *Client) ProjectVersionParameters(ctx context.Context, version uuid.UUID) ([]ProjectVersionParameter, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projectversions/%s/parameters", version), nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var params []ProjectVersionParameter
return params, json.NewDecoder(res.Body).Decode(&params)
}
// ProjectVersionResources returns resources a project version declares.
func (c *Client) ProjectVersionResources(ctx context.Context, version uuid.UUID) ([]WorkspaceResource, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projectversions/%s/resources", version), nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var resources []WorkspaceResource
return resources, json.NewDecoder(res.Body).Decode(&resources)
}
// ProjectVersionLogsBefore returns logs that occurred before a specific time.
func (c *Client) ProjectVersionLogsBefore(ctx context.Context, version uuid.UUID, before time.Time) ([]ProvisionerJobLog, error) {
return c.provisionerJobLogsBefore(ctx, fmt.Sprintf("/api/v2/projectversions/%s/logs", version), before)
}
// ProjectVersionLogsAfter streams logs for a project version that occurred after a specific time.
func (c *Client) ProjectVersionLogsAfter(ctx context.Context, version uuid.UUID, after time.Time) (<-chan ProvisionerJobLog, error) {
return c.provisionerJobLogsAfter(ctx, fmt.Sprintf("/api/v2/projectversions/%s/logs", version), after)
}

View File

@ -12,9 +12,9 @@ import (
"github.com/coder/coder/coderd/database"
)
// Project is the JSON representation of a Coder project. This type matches the
// Template is the JSON representation of a Coder template. This type matches the
// database object for now, but is abstracted for ease of change later on.
type Project struct {
type Template struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
@ -25,26 +25,26 @@ type Project struct {
WorkspaceOwnerCount uint32 `json:"workspace_owner_count"`
}
type UpdateActiveProjectVersion struct {
type UpdateActiveTemplateVersion struct {
ID uuid.UUID `json:"id" validate:"required"`
}
// Project returns a single project.
func (c *Client) Project(ctx context.Context, project uuid.UUID) (Project, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projects/%s", project), nil)
// Template returns a single template.
func (c *Client) Template(ctx context.Context, template uuid.UUID) (Template, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templates/%s", template), nil)
if err != nil {
return Project{}, nil
return Template{}, nil
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return Project{}, readBodyAsError(res)
return Template{}, readBodyAsError(res)
}
var resp Project
var resp Template
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
func (c *Client) DeleteProject(ctx context.Context, project uuid.UUID) error {
res, err := c.request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/projects/%s", project), nil)
func (c *Client) DeleteTemplate(ctx context.Context, template uuid.UUID) error {
res, err := c.request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/templates/%s", template), nil)
if err != nil {
return err
}
@ -55,10 +55,10 @@ func (c *Client) DeleteProject(ctx context.Context, project uuid.UUID) error {
return nil
}
// UpdateActiveProjectVersion updates the active project version to the ID provided.
// The project version must be attached to the project.
func (c *Client) UpdateActiveProjectVersion(ctx context.Context, project uuid.UUID, req UpdateActiveProjectVersion) error {
res, err := c.request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/projects/%s/versions", project), req)
// UpdateActiveTemplateVersion updates the active template version to the ID provided.
// The template version must be attached to the template.
func (c *Client) UpdateActiveTemplateVersion(ctx context.Context, template uuid.UUID, req UpdateActiveTemplateVersion) error {
res, err := c.request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/templates/%s/versions", template), req)
if err != nil {
return nil
}
@ -69,9 +69,9 @@ func (c *Client) UpdateActiveProjectVersion(ctx context.Context, project uuid.UU
return nil
}
// ProjectVersionsByProject lists versions associated with a project.
func (c *Client) ProjectVersionsByProject(ctx context.Context, project uuid.UUID) ([]ProjectVersion, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projects/%s/versions", project), nil)
// TemplateVersionsByTemplate lists versions associated with a template.
func (c *Client) TemplateVersionsByTemplate(ctx context.Context, template uuid.UUID) ([]TemplateVersion, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templates/%s/versions", template), nil)
if err != nil {
return nil, err
}
@ -79,21 +79,21 @@ func (c *Client) ProjectVersionsByProject(ctx context.Context, project uuid.UUID
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var projectVersion []ProjectVersion
return projectVersion, json.NewDecoder(res.Body).Decode(&projectVersion)
var templateVersion []TemplateVersion
return templateVersion, json.NewDecoder(res.Body).Decode(&templateVersion)
}
// ProjectVersionByName returns a project version by it's friendly name.
// This is used for path-based routing. Like: /projects/example/versions/helloworld
func (c *Client) ProjectVersionByName(ctx context.Context, project uuid.UUID, name string) (ProjectVersion, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projects/%s/versions/%s", project, name), nil)
// TemplateVersionByName returns a template version by it's friendly name.
// This is used for path-based routing. Like: /templates/example/versions/helloworld
func (c *Client) TemplateVersionByName(ctx context.Context, template uuid.UUID, name string) (TemplateVersion, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templates/%s/versions/%s", template, name), nil)
if err != nil {
return ProjectVersion{}, err
return TemplateVersion{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return ProjectVersion{}, readBodyAsError(res)
return TemplateVersion{}, readBodyAsError(res)
}
var projectVersion ProjectVersion
return projectVersion, json.NewDecoder(res.Body).Decode(&projectVersion)
var templateVersion TemplateVersion
return templateVersion, json.NewDecoder(res.Body).Decode(&templateVersion)
}

View File

@ -0,0 +1,109 @@
package codersdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/google/uuid"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/parameter"
)
// TemplateVersion represents a single version of a template.
type TemplateVersion struct {
ID uuid.UUID `json:"id"`
TemplateID *uuid.UUID `json:"template_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Name string `json:"name"`
Job ProvisionerJob `json:"job"`
}
// TemplateVersionParameterSchema represents a parameter parsed from template version source.
type TemplateVersionParameterSchema database.ParameterSchema
// TemplateVersionParameter represents a computed parameter value.
type TemplateVersionParameter parameter.ComputedValue
// TemplateVersion returns a template version by ID.
func (c *Client) TemplateVersion(ctx context.Context, id uuid.UUID) (TemplateVersion, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s", id), nil)
if err != nil {
return TemplateVersion{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return TemplateVersion{}, readBodyAsError(res)
}
var version TemplateVersion
return version, json.NewDecoder(res.Body).Decode(&version)
}
// CancelTemplateVersion marks a template version job as canceled.
func (c *Client) CancelTemplateVersion(ctx context.Context, version uuid.UUID) error {
res, err := c.request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/templateversions/%s/cancel", version), nil)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return readBodyAsError(res)
}
return nil
}
// TemplateVersionSchema returns schemas for a template version by ID.
func (c *Client) TemplateVersionSchema(ctx context.Context, version uuid.UUID) ([]TemplateVersionParameterSchema, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/schema", version), nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var params []TemplateVersionParameterSchema
return params, json.NewDecoder(res.Body).Decode(&params)
}
// TemplateVersionParameters returns computed parameters for a template version.
func (c *Client) TemplateVersionParameters(ctx context.Context, version uuid.UUID) ([]TemplateVersionParameter, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/parameters", version), nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var params []TemplateVersionParameter
return params, json.NewDecoder(res.Body).Decode(&params)
}
// TemplateVersionResources returns resources a template version declares.
func (c *Client) TemplateVersionResources(ctx context.Context, version uuid.UUID) ([]WorkspaceResource, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/resources", version), nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var resources []WorkspaceResource
return resources, json.NewDecoder(res.Body).Decode(&resources)
}
// TemplateVersionLogsBefore returns logs that occurred before a specific time.
func (c *Client) TemplateVersionLogsBefore(ctx context.Context, version uuid.UUID, before time.Time) ([]ProvisionerJobLog, error) {
return c.provisionerJobLogsBefore(ctx, fmt.Sprintf("/api/v2/templateversions/%s/logs", version), before)
}
// TemplateVersionLogsAfter streams logs for a template version that occurred after a specific time.
func (c *Client) TemplateVersionLogsAfter(ctx context.Context, version uuid.UUID, after time.Time) (<-chan ProvisionerJobLog, error) {
return c.provisionerJobLogsAfter(ctx, fmt.Sprintf("/api/v2/templateversions/%s/logs", version), after)
}

View File

@ -63,8 +63,8 @@ type CreateOrganizationRequest struct {
// CreateWorkspaceRequest provides options for creating a new workspace.
type CreateWorkspaceRequest struct {
ProjectID uuid.UUID `json:"project_id" validate:"required"`
Name string `json:"name" validate:"username,required"`
TemplateID uuid.UUID `json:"template_id" validate:"required"`
Name string `json:"name" validate:"username,required"`
// ParameterValues allows for additional parameters to be provided
// during the initial provision.
ParameterValues []CreateParameterRequest `json:"parameter_values"`
@ -219,7 +219,7 @@ func (c *Client) CreateOrganization(ctx context.Context, userID uuid.UUID, req C
return org, json.NewDecoder(res.Body).Decode(&org)
}
// CreateWorkspace creates a new workspace for the project specified.
// CreateWorkspace creates a new workspace for the template specified.
func (c *Client) CreateWorkspace(ctx context.Context, userID uuid.UUID, request CreateWorkspaceRequest) (Workspace, error) {
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/workspaces", uuidOrMe(userID)), request)
if err != nil {

View File

@ -15,17 +15,17 @@ import (
// WorkspaceBuild is an at-point representation of a workspace state.
// Iterate on before/after to determine a chronological history.
type WorkspaceBuild struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
WorkspaceID uuid.UUID `json:"workspace_id"`
ProjectVersionID uuid.UUID `json:"project_version_id"`
BeforeID uuid.UUID `json:"before_id"`
AfterID uuid.UUID `json:"after_id"`
Name string `json:"name"`
Transition database.WorkspaceTransition `json:"transition"`
InitiatorID uuid.UUID `json:"initiator_id"`
Job ProvisionerJob `json:"job"`
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
WorkspaceID uuid.UUID `json:"workspace_id"`
TemplateVersionID uuid.UUID `json:"template_version_id"`
BeforeID uuid.UUID `json:"before_id"`
AfterID uuid.UUID `json:"after_id"`
Name string `json:"name"`
Transition database.WorkspaceTransition `json:"transition"`
InitiatorID uuid.UUID `json:"initiator_id"`
Job ProvisionerJob `json:"job"`
}
// WorkspaceBuild returns a single workspace build for a workspace.

View File

@ -12,25 +12,25 @@ import (
"github.com/coder/coder/coderd/database"
)
// Workspace is a per-user deployment of a project. It tracks
// project versions, and can be updated.
// Workspace is a per-user deployment of a template. It tracks
// template versions, and can be updated.
type Workspace struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
OwnerID uuid.UUID `json:"owner_id"`
ProjectID uuid.UUID `json:"project_id"`
ProjectName string `json:"project_name"`
LatestBuild WorkspaceBuild `json:"latest_build"`
Outdated bool `json:"outdated"`
Name string `json:"name"`
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
OwnerID uuid.UUID `json:"owner_id"`
TemplateID uuid.UUID `json:"template_id"`
TemplateName string `json:"template_name"`
LatestBuild WorkspaceBuild `json:"latest_build"`
Outdated bool `json:"outdated"`
Name string `json:"name"`
}
// CreateWorkspaceBuildRequest provides options to update the latest workspace build.
type CreateWorkspaceBuildRequest struct {
ProjectVersionID uuid.UUID `json:"project_version_id"`
Transition database.WorkspaceTransition `json:"transition" validate:"oneof=create start stop delete,required"`
DryRun bool `json:"dry_run"`
TemplateVersionID uuid.UUID `json:"template_version_id"`
Transition database.WorkspaceTransition `json:"transition" validate:"oneof=create start stop delete,required"`
DryRun bool `json:"dry_run"`
}
// Workspace returns a single workspace.

View File

@ -113,14 +113,14 @@ type AcquiredJob struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
CreatedAt int64 `protobuf:"varint,2,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
Provisioner string `protobuf:"bytes,3,opt,name=provisioner,proto3" json:"provisioner,omitempty"`
UserName string `protobuf:"bytes,4,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"`
ProjectSourceArchive []byte `protobuf:"bytes,5,opt,name=project_source_archive,json=projectSourceArchive,proto3" json:"project_source_archive,omitempty"`
JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
CreatedAt int64 `protobuf:"varint,2,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
Provisioner string `protobuf:"bytes,3,opt,name=provisioner,proto3" json:"provisioner,omitempty"`
UserName string `protobuf:"bytes,4,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"`
TemplateSourceArchive []byte `protobuf:"bytes,5,opt,name=template_source_archive,json=templateSourceArchive,proto3" json:"template_source_archive,omitempty"`
// Types that are assignable to Type:
// *AcquiredJob_WorkspaceBuild_
// *AcquiredJob_ProjectImport_
// *AcquiredJob_TemplateImport_
Type isAcquiredJob_Type `protobuf_oneof:"type"`
}
@ -184,9 +184,9 @@ func (x *AcquiredJob) GetUserName() string {
return ""
}
func (x *AcquiredJob) GetProjectSourceArchive() []byte {
func (x *AcquiredJob) GetTemplateSourceArchive() []byte {
if x != nil {
return x.ProjectSourceArchive
return x.TemplateSourceArchive
}
return nil
}
@ -205,9 +205,9 @@ func (x *AcquiredJob) GetWorkspaceBuild() *AcquiredJob_WorkspaceBuild {
return nil
}
func (x *AcquiredJob) GetProjectImport() *AcquiredJob_ProjectImport {
if x, ok := x.GetType().(*AcquiredJob_ProjectImport_); ok {
return x.ProjectImport
func (x *AcquiredJob) GetTemplateImport() *AcquiredJob_TemplateImport {
if x, ok := x.GetType().(*AcquiredJob_TemplateImport_); ok {
return x.TemplateImport
}
return nil
}
@ -220,13 +220,13 @@ type AcquiredJob_WorkspaceBuild_ struct {
WorkspaceBuild *AcquiredJob_WorkspaceBuild `protobuf:"bytes,6,opt,name=workspace_build,json=workspaceBuild,proto3,oneof"`
}
type AcquiredJob_ProjectImport_ struct {
ProjectImport *AcquiredJob_ProjectImport `protobuf:"bytes,7,opt,name=project_import,json=projectImport,proto3,oneof"`
type AcquiredJob_TemplateImport_ struct {
TemplateImport *AcquiredJob_TemplateImport `protobuf:"bytes,7,opt,name=template_import,json=templateImport,proto3,oneof"`
}
func (*AcquiredJob_WorkspaceBuild_) isAcquiredJob_Type() {}
func (*AcquiredJob_ProjectImport_) isAcquiredJob_Type() {}
func (*AcquiredJob_TemplateImport_) isAcquiredJob_Type() {}
type FailedJob struct {
state protoimpl.MessageState
@ -237,7 +237,7 @@ type FailedJob struct {
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
// Types that are assignable to Type:
// *FailedJob_WorkspaceBuild_
// *FailedJob_ProjectImport_
// *FailedJob_TemplateImport_
Type isFailedJob_Type `protobuf_oneof:"type"`
}
@ -301,9 +301,9 @@ func (x *FailedJob) GetWorkspaceBuild() *FailedJob_WorkspaceBuild {
return nil
}
func (x *FailedJob) GetProjectImport() *FailedJob_ProjectImport {
if x, ok := x.GetType().(*FailedJob_ProjectImport_); ok {
return x.ProjectImport
func (x *FailedJob) GetTemplateImport() *FailedJob_TemplateImport {
if x, ok := x.GetType().(*FailedJob_TemplateImport_); ok {
return x.TemplateImport
}
return nil
}
@ -316,13 +316,13 @@ type FailedJob_WorkspaceBuild_ struct {
WorkspaceBuild *FailedJob_WorkspaceBuild `protobuf:"bytes,3,opt,name=workspace_build,json=workspaceBuild,proto3,oneof"`
}
type FailedJob_ProjectImport_ struct {
ProjectImport *FailedJob_ProjectImport `protobuf:"bytes,4,opt,name=project_import,json=projectImport,proto3,oneof"`
type FailedJob_TemplateImport_ struct {
TemplateImport *FailedJob_TemplateImport `protobuf:"bytes,4,opt,name=template_import,json=templateImport,proto3,oneof"`
}
func (*FailedJob_WorkspaceBuild_) isFailedJob_Type() {}
func (*FailedJob_ProjectImport_) isFailedJob_Type() {}
func (*FailedJob_TemplateImport_) isFailedJob_Type() {}
// CompletedJob is sent when the provisioner daemon completes a job.
type CompletedJob struct {
@ -333,7 +333,7 @@ type CompletedJob struct {
JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
// Types that are assignable to Type:
// *CompletedJob_WorkspaceBuild_
// *CompletedJob_ProjectImport_
// *CompletedJob_TemplateImport_
Type isCompletedJob_Type `protobuf_oneof:"type"`
}
@ -390,9 +390,9 @@ func (x *CompletedJob) GetWorkspaceBuild() *CompletedJob_WorkspaceBuild {
return nil
}
func (x *CompletedJob) GetProjectImport() *CompletedJob_ProjectImport {
if x, ok := x.GetType().(*CompletedJob_ProjectImport_); ok {
return x.ProjectImport
func (x *CompletedJob) GetTemplateImport() *CompletedJob_TemplateImport {
if x, ok := x.GetType().(*CompletedJob_TemplateImport_); ok {
return x.TemplateImport
}
return nil
}
@ -405,13 +405,13 @@ type CompletedJob_WorkspaceBuild_ struct {
WorkspaceBuild *CompletedJob_WorkspaceBuild `protobuf:"bytes,2,opt,name=workspace_build,json=workspaceBuild,proto3,oneof"`
}
type CompletedJob_ProjectImport_ struct {
ProjectImport *CompletedJob_ProjectImport `protobuf:"bytes,3,opt,name=project_import,json=projectImport,proto3,oneof"`
type CompletedJob_TemplateImport_ struct {
TemplateImport *CompletedJob_TemplateImport `protobuf:"bytes,3,opt,name=template_import,json=templateImport,proto3,oneof"`
}
func (*CompletedJob_WorkspaceBuild_) isCompletedJob_Type() {}
func (*CompletedJob_ProjectImport_) isCompletedJob_Type() {}
func (*CompletedJob_TemplateImport_) isCompletedJob_Type() {}
// Log represents output from a job.
type Log struct {
@ -693,7 +693,7 @@ func (x *AcquiredJob_WorkspaceBuild) GetState() []byte {
return nil
}
type AcquiredJob_ProjectImport struct {
type AcquiredJob_TemplateImport struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
@ -701,8 +701,8 @@ type AcquiredJob_ProjectImport struct {
Metadata *proto.Provision_Metadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
}
func (x *AcquiredJob_ProjectImport) Reset() {
*x = AcquiredJob_ProjectImport{}
func (x *AcquiredJob_TemplateImport) Reset() {
*x = AcquiredJob_TemplateImport{}
if protoimpl.UnsafeEnabled {
mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@ -710,13 +710,13 @@ func (x *AcquiredJob_ProjectImport) Reset() {
}
}
func (x *AcquiredJob_ProjectImport) String() string {
func (x *AcquiredJob_TemplateImport) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AcquiredJob_ProjectImport) ProtoMessage() {}
func (*AcquiredJob_TemplateImport) ProtoMessage() {}
func (x *AcquiredJob_ProjectImport) ProtoReflect() protoreflect.Message {
func (x *AcquiredJob_TemplateImport) ProtoReflect() protoreflect.Message {
mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@ -728,12 +728,12 @@ func (x *AcquiredJob_ProjectImport) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use AcquiredJob_ProjectImport.ProtoReflect.Descriptor instead.
func (*AcquiredJob_ProjectImport) Descriptor() ([]byte, []int) {
// Deprecated: Use AcquiredJob_TemplateImport.ProtoReflect.Descriptor instead.
func (*AcquiredJob_TemplateImport) Descriptor() ([]byte, []int) {
return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{1, 1}
}
func (x *AcquiredJob_ProjectImport) GetMetadata() *proto.Provision_Metadata {
func (x *AcquiredJob_TemplateImport) GetMetadata() *proto.Provision_Metadata {
if x != nil {
return x.Metadata
}
@ -787,14 +787,14 @@ func (x *FailedJob_WorkspaceBuild) GetState() []byte {
return nil
}
type FailedJob_ProjectImport struct {
type FailedJob_TemplateImport struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *FailedJob_ProjectImport) Reset() {
*x = FailedJob_ProjectImport{}
func (x *FailedJob_TemplateImport) Reset() {
*x = FailedJob_TemplateImport{}
if protoimpl.UnsafeEnabled {
mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@ -802,13 +802,13 @@ func (x *FailedJob_ProjectImport) Reset() {
}
}
func (x *FailedJob_ProjectImport) String() string {
func (x *FailedJob_TemplateImport) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FailedJob_ProjectImport) ProtoMessage() {}
func (*FailedJob_TemplateImport) ProtoMessage() {}
func (x *FailedJob_ProjectImport) ProtoReflect() protoreflect.Message {
func (x *FailedJob_TemplateImport) ProtoReflect() protoreflect.Message {
mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@ -820,8 +820,8 @@ func (x *FailedJob_ProjectImport) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use FailedJob_ProjectImport.ProtoReflect.Descriptor instead.
func (*FailedJob_ProjectImport) Descriptor() ([]byte, []int) {
// Deprecated: Use FailedJob_TemplateImport.ProtoReflect.Descriptor instead.
func (*FailedJob_TemplateImport) Descriptor() ([]byte, []int) {
return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{2, 1}
}
@ -880,7 +880,7 @@ func (x *CompletedJob_WorkspaceBuild) GetResources() []*proto.Resource {
return nil
}
type CompletedJob_ProjectImport struct {
type CompletedJob_TemplateImport struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
@ -889,8 +889,8 @@ type CompletedJob_ProjectImport struct {
StopResources []*proto.Resource `protobuf:"bytes,2,rep,name=stop_resources,json=stopResources,proto3" json:"stop_resources,omitempty"`
}
func (x *CompletedJob_ProjectImport) Reset() {
*x = CompletedJob_ProjectImport{}
func (x *CompletedJob_TemplateImport) Reset() {
*x = CompletedJob_TemplateImport{}
if protoimpl.UnsafeEnabled {
mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@ -898,13 +898,13 @@ func (x *CompletedJob_ProjectImport) Reset() {
}
}
func (x *CompletedJob_ProjectImport) String() string {
func (x *CompletedJob_TemplateImport) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CompletedJob_ProjectImport) ProtoMessage() {}
func (*CompletedJob_TemplateImport) ProtoMessage() {}
func (x *CompletedJob_ProjectImport) ProtoReflect() protoreflect.Message {
func (x *CompletedJob_TemplateImport) ProtoReflect() protoreflect.Message {
mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[12]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@ -916,19 +916,19 @@ func (x *CompletedJob_ProjectImport) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use CompletedJob_ProjectImport.ProtoReflect.Descriptor instead.
func (*CompletedJob_ProjectImport) Descriptor() ([]byte, []int) {
// Deprecated: Use CompletedJob_TemplateImport.ProtoReflect.Descriptor instead.
func (*CompletedJob_TemplateImport) Descriptor() ([]byte, []int) {
return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{3, 1}
}
func (x *CompletedJob_ProjectImport) GetStartResources() []*proto.Resource {
func (x *CompletedJob_TemplateImport) GetStartResources() []*proto.Resource {
if x != nil {
return x.StartResources
}
return nil
}
func (x *CompletedJob_ProjectImport) GetStopResources() []*proto.Resource {
func (x *CompletedJob_TemplateImport) GetStopResources() []*proto.Resource {
if x != nil {
return x.StopResources
}
@ -944,7 +944,7 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{
0x6f, 0x6e, 0x65, 0x72, 0x64, 0x1a, 0x26, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x76,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a,
0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xb8, 0x05, 0x0a, 0x0b, 0x41, 0x63, 0x71, 0x75, 0x69,
0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xbe, 0x05, 0x0a, 0x0b, 0x41, 0x63, 0x71, 0x75, 0x69,
0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1d, 0x0a,
0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
@ -952,142 +952,143 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1b,
0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x70,
0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x72,
0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x70, 0x72, 0x6f,
0x6a, 0x65, 0x63, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76,
0x65, 0x12, 0x53, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62,
0x75, 0x69, 0x6c, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x6f,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72,
0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42,
0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x50, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63,
0x74, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27,
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63,
0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63,
0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65,
0x63, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x80, 0x02, 0x0a, 0x0e, 0x57, 0x6f, 0x72,
0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77,
0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72,
0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65,
0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74,
0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74,
0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61,
0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74,
0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x1a, 0x4c, 0x0a, 0x0d, 0x50,
0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3b, 0x0a, 0x08,
0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f,
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52,
0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70,
0x65, 0x22, 0x9c, 0x02, 0x0a, 0x09, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12,
0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x51, 0x0a, 0x0f,
0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57,
0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, 0x52,
0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12,
0x4e, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72,
0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62,
0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00,
0x52, 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x1a,
0x26, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c,
0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x1a, 0x0f, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6a, 0x65,
0x63, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
0x22, 0xc3, 0x03, 0x0a, 0x0c, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f,
0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b,
0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x74,
0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61,
0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x74, 0x65,
0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x72, 0x63, 0x68,
0x69, 0x76, 0x65, 0x12, 0x53, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75,
0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70,
0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x53, 0x0a, 0x0f, 0x74, 0x65, 0x6d, 0x70,
0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64,
0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d,
0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x74,
0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x80, 0x02,
0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64,
0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75,
0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f,
0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x12, 0x25,
0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74,
0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61,
0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61,
0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x3b, 0x0a,
0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74,
0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65,
0x1a, 0x4d, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f,
0x72, 0x74, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74,
0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42,
0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xa0, 0x02, 0x0a, 0x09, 0x46, 0x61, 0x69, 0x6c,
0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05,
0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72,
0x6f, 0x72, 0x12, 0x51, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f,
0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65,
0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75,
0x69, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x51, 0x0a, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74,
0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26,
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61,
0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65,
0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61,
0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x26, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b,
0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74,
0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65,
0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f,
0x72, 0x74, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xc7, 0x03, 0x0a, 0x0c, 0x43,
0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a,
0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62,
0x49, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f,
0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c,
0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70,
0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x74, 0x65, 0x6d, 0x70,
0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64,
0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f,
0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e,
0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x51,
0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74,
0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a,
0x6f, 0x62, 0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74,
0x48, 0x00, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72,
0x74, 0x1a, 0x5b, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75,
0x69, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x1a, 0x8d,
0x01, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74,
0x12, 0x3e, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76,
0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65,
0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, 0x0e,
0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x5b,
0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64,
0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
0x12, 0x3c, 0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52,
0x0d, 0x73, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x42, 0x06,
0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xb0, 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x2f,
0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17,
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f,
0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12,
0x2b, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15,
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67,
0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a,
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73,
0x74, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67,
0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28,
0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x9b, 0x01, 0x0a, 0x10, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15,
0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
0x72, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x49, 0x0a, 0x11,
0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53,
0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72,
0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x22, 0x77, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08,
0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08,
0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61,
0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73,
0x2a, 0x34, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a,
0x12, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x5f, 0x44, 0x41, 0x45,
0x4d, 0x4f, 0x4e, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49,
0x4f, 0x4e, 0x45, 0x52, 0x10, 0x01, 0x32, 0x98, 0x02, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69,
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x0a,
0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41,
0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x4c, 0x0a, 0x09, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c,
0x4a, 0x6f, 0x62, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74,
0x79, 0x12, 0x3e, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62,
0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e,
0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74,
0x79, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x76,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x1a, 0x8e, 0x01, 0x0a, 0x0e,
0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3e,
0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0e,
0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3c,
0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0d, 0x73,
0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x42, 0x06, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x22, 0xb0, 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x2f, 0x0a, 0x06,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x53,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2b, 0x0a,
0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65,
0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72,
0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09,
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61,
0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12,
0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x9b, 0x01, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06,
0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f,
0x62, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64,
0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x49, 0x0a, 0x11, 0x70, 0x61,
0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18,
0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68,
0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63,
0x68, 0x65, 0x6d, 0x61, 0x73, 0x22, 0x77, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a,
0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61,
0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x63, 0x61,
0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65,
0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50,
0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70,
0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2a, 0x34,
0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x50,
0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x5f, 0x44, 0x41, 0x45, 0x4d, 0x4f,
0x4e, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e,
0x45, 0x52, 0x10, 0x01, 0x32, 0x98, 0x02, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6f, 0x6e, 0x65, 0x72, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x0a, 0x41, 0x63,
0x71, 0x75, 0x69, 0x72, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e,
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71,
0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x4c, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c, 0x4a, 0x6f,
0x62, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64,
0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12,
0x3e, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1a,
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f,
0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42,
0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f,
0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -1114,11 +1115,11 @@ var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{
(*UpdateJobRequest)(nil), // 6: provisionerd.UpdateJobRequest
(*UpdateJobResponse)(nil), // 7: provisionerd.UpdateJobResponse
(*AcquiredJob_WorkspaceBuild)(nil), // 8: provisionerd.AcquiredJob.WorkspaceBuild
(*AcquiredJob_ProjectImport)(nil), // 9: provisionerd.AcquiredJob.ProjectImport
(*AcquiredJob_TemplateImport)(nil), // 9: provisionerd.AcquiredJob.TemplateImport
(*FailedJob_WorkspaceBuild)(nil), // 10: provisionerd.FailedJob.WorkspaceBuild
(*FailedJob_ProjectImport)(nil), // 11: provisionerd.FailedJob.ProjectImport
(*FailedJob_TemplateImport)(nil), // 11: provisionerd.FailedJob.TemplateImport
(*CompletedJob_WorkspaceBuild)(nil), // 12: provisionerd.CompletedJob.WorkspaceBuild
(*CompletedJob_ProjectImport)(nil), // 13: provisionerd.CompletedJob.ProjectImport
(*CompletedJob_TemplateImport)(nil), // 13: provisionerd.CompletedJob.TemplateImport
(proto.LogLevel)(0), // 14: provisioner.LogLevel
(*proto.ParameterSchema)(nil), // 15: provisioner.ParameterSchema
(*proto.ParameterValue)(nil), // 16: provisioner.ParameterValue
@ -1127,11 +1128,11 @@ var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{
}
var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{
8, // 0: provisionerd.AcquiredJob.workspace_build:type_name -> provisionerd.AcquiredJob.WorkspaceBuild
9, // 1: provisionerd.AcquiredJob.project_import:type_name -> provisionerd.AcquiredJob.ProjectImport
9, // 1: provisionerd.AcquiredJob.template_import:type_name -> provisionerd.AcquiredJob.TemplateImport
10, // 2: provisionerd.FailedJob.workspace_build:type_name -> provisionerd.FailedJob.WorkspaceBuild
11, // 3: provisionerd.FailedJob.project_import:type_name -> provisionerd.FailedJob.ProjectImport
11, // 3: provisionerd.FailedJob.template_import:type_name -> provisionerd.FailedJob.TemplateImport
12, // 4: provisionerd.CompletedJob.workspace_build:type_name -> provisionerd.CompletedJob.WorkspaceBuild
13, // 5: provisionerd.CompletedJob.project_import:type_name -> provisionerd.CompletedJob.ProjectImport
13, // 5: provisionerd.CompletedJob.template_import:type_name -> provisionerd.CompletedJob.TemplateImport
0, // 6: provisionerd.Log.source:type_name -> provisionerd.LogSource
14, // 7: provisionerd.Log.level:type_name -> provisioner.LogLevel
5, // 8: provisionerd.UpdateJobRequest.logs:type_name -> provisionerd.Log
@ -1139,10 +1140,10 @@ var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{
16, // 10: provisionerd.UpdateJobResponse.parameter_values:type_name -> provisioner.ParameterValue
16, // 11: provisionerd.AcquiredJob.WorkspaceBuild.parameter_values:type_name -> provisioner.ParameterValue
17, // 12: provisionerd.AcquiredJob.WorkspaceBuild.metadata:type_name -> provisioner.Provision.Metadata
17, // 13: provisionerd.AcquiredJob.ProjectImport.metadata:type_name -> provisioner.Provision.Metadata
17, // 13: provisionerd.AcquiredJob.TemplateImport.metadata:type_name -> provisioner.Provision.Metadata
18, // 14: provisionerd.CompletedJob.WorkspaceBuild.resources:type_name -> provisioner.Resource
18, // 15: provisionerd.CompletedJob.ProjectImport.start_resources:type_name -> provisioner.Resource
18, // 16: provisionerd.CompletedJob.ProjectImport.stop_resources:type_name -> provisioner.Resource
18, // 15: provisionerd.CompletedJob.TemplateImport.start_resources:type_name -> provisioner.Resource
18, // 16: provisionerd.CompletedJob.TemplateImport.stop_resources:type_name -> provisioner.Resource
1, // 17: provisionerd.ProvisionerDaemon.AcquireJob:input_type -> provisionerd.Empty
6, // 18: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.UpdateJobRequest
3, // 19: provisionerd.ProvisionerDaemon.FailJob:input_type -> provisionerd.FailedJob
@ -1261,7 +1262,7 @@ func file_provisionerd_proto_provisionerd_proto_init() {
}
}
file_provisionerd_proto_provisionerd_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AcquiredJob_ProjectImport); i {
switch v := v.(*AcquiredJob_TemplateImport); i {
case 0:
return &v.state
case 1:
@ -1285,7 +1286,7 @@ func file_provisionerd_proto_provisionerd_proto_init() {
}
}
file_provisionerd_proto_provisionerd_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FailedJob_ProjectImport); i {
switch v := v.(*FailedJob_TemplateImport); i {
case 0:
return &v.state
case 1:
@ -1309,7 +1310,7 @@ func file_provisionerd_proto_provisionerd_proto_init() {
}
}
file_provisionerd_proto_provisionerd_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CompletedJob_ProjectImport); i {
switch v := v.(*CompletedJob_TemplateImport); i {
case 0:
return &v.state
case 1:
@ -1323,15 +1324,15 @@ func file_provisionerd_proto_provisionerd_proto_init() {
}
file_provisionerd_proto_provisionerd_proto_msgTypes[1].OneofWrappers = []interface{}{
(*AcquiredJob_WorkspaceBuild_)(nil),
(*AcquiredJob_ProjectImport_)(nil),
(*AcquiredJob_TemplateImport_)(nil),
}
file_provisionerd_proto_provisionerd_proto_msgTypes[2].OneofWrappers = []interface{}{
(*FailedJob_WorkspaceBuild_)(nil),
(*FailedJob_ProjectImport_)(nil),
(*FailedJob_TemplateImport_)(nil),
}
file_provisionerd_proto_provisionerd_proto_msgTypes[3].OneofWrappers = []interface{}{
(*CompletedJob_WorkspaceBuild_)(nil),
(*CompletedJob_ProjectImport_)(nil),
(*CompletedJob_TemplateImport_)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{

View File

@ -18,17 +18,17 @@ message AcquiredJob {
provisioner.Provision.Metadata metadata = 4;
bytes state = 5;
}
message ProjectImport {
message TemplateImport {
provisioner.Provision.Metadata metadata = 1;
}
string job_id = 1;
int64 created_at = 2;
string provisioner = 3;
string user_name = 4;
bytes project_source_archive = 5;
bytes template_source_archive = 5;
oneof type {
WorkspaceBuild workspace_build = 6;
ProjectImport project_import = 7;
TemplateImport template_import = 7;
}
}
@ -36,13 +36,13 @@ message FailedJob {
message WorkspaceBuild {
bytes state = 1;
}
message ProjectImport{
message TemplateImport{
}
string job_id = 1;
string error = 2;
oneof type {
WorkspaceBuild workspace_build = 3;
ProjectImport project_import = 4;
TemplateImport template_import = 4;
}
}
@ -52,14 +52,14 @@ message CompletedJob {
bytes state = 1;
repeated provisioner.Resource resources = 2;
}
message ProjectImport {
message TemplateImport {
repeated provisioner.Resource start_resources = 1;
repeated provisioner.Resource stop_resources = 2;
}
string job_id = 1;
oneof type {
WorkspaceBuild workspace_build = 2;
ProjectImport project_import = 3;
TemplateImport template_import = 3;
}
}

View File

@ -312,15 +312,15 @@ func (p *Server) runJob(ctx context.Context, job *proto.AcquiredJob) {
return
}
p.opts.Logger.Info(ctx, "unpacking project source archive", slog.F("size_bytes", len(job.ProjectSourceArchive)))
reader := tar.NewReader(bytes.NewBuffer(job.ProjectSourceArchive))
p.opts.Logger.Info(ctx, "unpacking template source archive", slog.F("size_bytes", len(job.TemplateSourceArchive)))
reader := tar.NewReader(bytes.NewBuffer(job.TemplateSourceArchive))
for {
header, err := reader.Next()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
p.failActiveJobf("read project source archive: %s", err)
p.failActiveJobf("read template source archive: %s", err)
return
}
// #nosec
@ -371,10 +371,10 @@ func (p *Server) runJob(ctx context.Context, job *proto.AcquiredJob) {
}
switch jobType := job.Type.(type) {
case *proto.AcquiredJob_ProjectImport_:
p.opts.Logger.Debug(context.Background(), "acquired job is project import")
case *proto.AcquiredJob_TemplateImport_:
p.opts.Logger.Debug(context.Background(), "acquired job is template import")
p.runProjectImport(ctx, shutdown, provisioner, job)
p.runTemplateImport(ctx, shutdown, provisioner, job)
case *proto.AcquiredJob_WorkspaceBuild_:
p.opts.Logger.Debug(context.Background(), "acquired job is workspace provision",
slog.F("workspace_name", jobType.WorkspaceBuild.WorkspaceName),
@ -409,7 +409,7 @@ func (p *Server) runJob(ctx context.Context, job *proto.AcquiredJob) {
}
}
func (p *Server) runProjectImport(ctx, shutdown context.Context, provisioner sdkproto.DRPCProvisionerClient, job *proto.AcquiredJob) {
func (p *Server) runTemplateImport(ctx, shutdown context.Context, provisioner sdkproto.DRPCProvisionerClient, job *proto.AcquiredJob) {
_, err := p.client.UpdateJob(ctx, &proto.UpdateJobRequest{
JobId: job.GetJobId(),
Logs: []*proto.Log{{
@ -424,7 +424,7 @@ func (p *Server) runProjectImport(ctx, shutdown context.Context, provisioner sdk
return
}
parameterSchemas, err := p.runProjectImportParse(ctx, provisioner, job)
parameterSchemas, err := p.runTemplateImportParse(ctx, provisioner, job)
if err != nil {
p.failActiveJobf("run parse: %s", err)
return
@ -464,12 +464,12 @@ func (p *Server) runProjectImport(ctx, shutdown context.Context, provisioner sdk
p.failActiveJobf("write log: %s", err)
return
}
startResources, err := p.runProjectImportProvision(ctx, shutdown, provisioner, job, updateResponse.ParameterValues, &sdkproto.Provision_Metadata{
CoderUrl: job.GetProjectImport().Metadata.CoderUrl,
startResources, err := p.runTemplateImportProvision(ctx, shutdown, provisioner, job, updateResponse.ParameterValues, &sdkproto.Provision_Metadata{
CoderUrl: job.GetTemplateImport().Metadata.CoderUrl,
WorkspaceTransition: sdkproto.WorkspaceTransition_START,
})
if err != nil {
p.failActiveJobf("project import provision for start: %s", err)
p.failActiveJobf("template import provision for start: %s", err)
return
}
_, err = p.client.UpdateJob(ctx, &proto.UpdateJobRequest{
@ -485,19 +485,19 @@ func (p *Server) runProjectImport(ctx, shutdown context.Context, provisioner sdk
p.failActiveJobf("write log: %s", err)
return
}
stopResources, err := p.runProjectImportProvision(ctx, shutdown, provisioner, job, updateResponse.ParameterValues, &sdkproto.Provision_Metadata{
CoderUrl: job.GetProjectImport().Metadata.CoderUrl,
stopResources, err := p.runTemplateImportProvision(ctx, shutdown, provisioner, job, updateResponse.ParameterValues, &sdkproto.Provision_Metadata{
CoderUrl: job.GetTemplateImport().Metadata.CoderUrl,
WorkspaceTransition: sdkproto.WorkspaceTransition_STOP,
})
if err != nil {
p.failActiveJobf("project import provision for start: %s", err)
p.failActiveJobf("template import provision for start: %s", err)
return
}
_, err = p.client.CompleteJob(ctx, &proto.CompletedJob{
JobId: job.JobId,
Type: &proto.CompletedJob_ProjectImport_{
ProjectImport: &proto.CompletedJob_ProjectImport{
Type: &proto.CompletedJob_TemplateImport_{
TemplateImport: &proto.CompletedJob_TemplateImport{
StartResources: startResources,
StopResources: stopResources,
},
@ -510,7 +510,7 @@ func (p *Server) runProjectImport(ctx, shutdown context.Context, provisioner sdk
}
// Parses parameter schemas from source.
func (p *Server) runProjectImportParse(ctx context.Context, provisioner sdkproto.DRPCProvisionerClient, job *proto.AcquiredJob) ([]*sdkproto.ParameterSchema, error) {
func (p *Server) runTemplateImportParse(ctx context.Context, provisioner sdkproto.DRPCProvisionerClient, job *proto.AcquiredJob) ([]*sdkproto.ParameterSchema, error) {
stream, err := provisioner.Parse(ctx, &sdkproto.Parse_Request{
Directory: p.opts.WorkDirectory,
})
@ -554,10 +554,10 @@ func (p *Server) runProjectImportParse(ctx context.Context, provisioner sdkproto
}
}
// Performs a dry-run provision when importing a project.
// Performs a dry-run provision when importing a template.
// This is used to detect resources that would be provisioned
// for a workspace in various states.
func (p *Server) runProjectImportProvision(ctx, shutdown context.Context, provisioner sdkproto.DRPCProvisionerClient, job *proto.AcquiredJob, values []*sdkproto.ParameterValue, metadata *sdkproto.Provision_Metadata) ([]*sdkproto.Resource, error) {
func (p *Server) runTemplateImportProvision(ctx, shutdown context.Context, provisioner sdkproto.DRPCProvisionerClient, job *proto.AcquiredJob, values []*sdkproto.ParameterValue, metadata *sdkproto.Provision_Metadata) ([]*sdkproto.Resource, error) {
stream, err := provisioner.Provision(ctx)
if err != nil {
return nil, xerrors.Errorf("provision: %w", err)
@ -596,7 +596,7 @@ func (p *Server) runProjectImportProvision(ctx, shutdown context.Context, provis
}
switch msgType := msg.Type.(type) {
case *sdkproto.Provision_Response_Log:
p.opts.Logger.Debug(context.Background(), "project import provision job logged",
p.opts.Logger.Debug(context.Background(), "template import provision job logged",
slog.F("level", msgType.Log.Level),
slog.F("output", msgType.Log.Output),
)

View File

@ -92,11 +92,11 @@ func TestProvisionerd(t *testing.T) {
return &proto.AcquiredJob{
JobId: "test",
Provisioner: "someprovisioner",
ProjectSourceArchive: createTar(t, map[string]string{
TemplateSourceArchive: createTar(t, map[string]string{
"test.txt": "content",
}),
Type: &proto.AcquiredJob_ProjectImport_{
ProjectImport: &proto.AcquiredJob_ProjectImport{
Type: &proto.AcquiredJob_TemplateImport_{
TemplateImport: &proto.AcquiredJob_TemplateImport{
Metadata: &sdkproto.Provision_Metadata{},
},
},
@ -133,11 +133,11 @@ func TestProvisionerd(t *testing.T) {
return &proto.AcquiredJob{
JobId: "test",
Provisioner: "someprovisioner",
ProjectSourceArchive: createTar(t, map[string]string{
TemplateSourceArchive: createTar(t, map[string]string{
"../../../etc/passwd": "content",
}),
Type: &proto.AcquiredJob_ProjectImport_{
ProjectImport: &proto.AcquiredJob_ProjectImport{
Type: &proto.AcquiredJob_TemplateImport_{
TemplateImport: &proto.AcquiredJob_TemplateImport{
Metadata: &sdkproto.Provision_Metadata{},
},
},
@ -165,11 +165,11 @@ func TestProvisionerd(t *testing.T) {
return &proto.AcquiredJob{
JobId: "test",
Provisioner: "someprovisioner",
ProjectSourceArchive: createTar(t, map[string]string{
TemplateSourceArchive: createTar(t, map[string]string{
"test.txt": "content",
}),
Type: &proto.AcquiredJob_ProjectImport_{
ProjectImport: &proto.AcquiredJob_ProjectImport{
Type: &proto.AcquiredJob_TemplateImport_{
TemplateImport: &proto.AcquiredJob_TemplateImport{
Metadata: &sdkproto.Provision_Metadata{},
},
},
@ -199,7 +199,7 @@ func TestProvisionerd(t *testing.T) {
require.NoError(t, closer.Close())
})
t.Run("ProjectImport", func(t *testing.T) {
t.Run("TemplateImport", func(t *testing.T) {
t.Parallel()
var (
didComplete atomic.Bool
@ -219,11 +219,11 @@ func TestProvisionerd(t *testing.T) {
return &proto.AcquiredJob{
JobId: "test",
Provisioner: "someprovisioner",
ProjectSourceArchive: createTar(t, map[string]string{
TemplateSourceArchive: createTar(t, map[string]string{
"test.txt": "content",
}),
Type: &proto.AcquiredJob_ProjectImport_{
ProjectImport: &proto.AcquiredJob_ProjectImport{
Type: &proto.AcquiredJob_TemplateImport_{
TemplateImport: &proto.AcquiredJob_TemplateImport{
Metadata: &sdkproto.Provision_Metadata{},
},
},
@ -319,7 +319,7 @@ func TestProvisionerd(t *testing.T) {
return &proto.AcquiredJob{
JobId: "test",
Provisioner: "someprovisioner",
ProjectSourceArchive: createTar(t, map[string]string{
TemplateSourceArchive: createTar(t, map[string]string{
"test.txt": "content",
}),
Type: &proto.AcquiredJob_WorkspaceBuild_{
@ -387,7 +387,7 @@ func TestProvisionerd(t *testing.T) {
return &proto.AcquiredJob{
JobId: "test",
Provisioner: "someprovisioner",
ProjectSourceArchive: createTar(t, map[string]string{
TemplateSourceArchive: createTar(t, map[string]string{
"test.txt": "content",
}),
Type: &proto.AcquiredJob_WorkspaceBuild_{
@ -431,7 +431,7 @@ func TestProvisionerd(t *testing.T) {
return &proto.AcquiredJob{
JobId: "test",
Provisioner: "someprovisioner",
ProjectSourceArchive: createTar(t, map[string]string{
TemplateSourceArchive: createTar(t, map[string]string{
"test.txt": "content",
}),
Type: &proto.AcquiredJob_WorkspaceBuild_{
@ -500,7 +500,7 @@ func TestProvisionerd(t *testing.T) {
return &proto.AcquiredJob{
JobId: "test",
Provisioner: "someprovisioner",
ProjectSourceArchive: createTar(t, map[string]string{
TemplateSourceArchive: createTar(t, map[string]string{
"test.txt": "content",
}),
Type: &proto.AcquiredJob_WorkspaceBuild_{

View File

@ -1,8 +1,8 @@
import { Page } from "@playwright/test"
import { BasePom } from "./BasePom"
export class ProjectsPage extends BasePom {
export class TemplatesPage extends BasePom {
constructor(baseURL: string | undefined, page: Page) {
super(baseURL, "/projects", page)
super(baseURL, "/templates", page)
}
}

View File

@ -1,2 +1,2 @@
export * from "./ProjectsPage"
export * from "./SignInPage"
export * from "./TemplatesPage"

View File

@ -1,17 +1,17 @@
import { test } from "@playwright/test"
import { email, password } from "../constants"
import { ProjectsPage, SignInPage } from "../pom"
import { SignInPage, TemplatesPage } from "../pom"
import { waitForClientSideNavigation } from "./../util"
test("Login takes user to /projects", async ({ baseURL, page }) => {
test("Login takes user to /templates", async ({ baseURL, page }) => {
await page.goto(baseURL + "/", { waitUntil: "networkidle" })
// Log-in with the default credentials we set up in the development server
const signInPage = new SignInPage(baseURL, page)
await signInPage.submitBuiltInAuthentication(email, password)
const projectsPage = new ProjectsPage(baseURL, page)
await waitForClientSideNavigation(page, { to: projectsPage.url })
const templatesPage = new TemplatesPage(baseURL, page)
await waitForClientSideNavigation(page, { to: templatesPage.url })
await page.waitForSelector("text=Projects")
await page.waitForSelector("text=Templates")
})

View File

@ -9,7 +9,7 @@
"build:analyze": "NODE_ENV=production webpack --profile --progress --json --config=webpack.prod.ts > out/stats.json && webpack-bundle-analyzer out/stats.json out",
"dev": "webpack-dev-server --config=webpack.dev.ts",
"format:check": "prettier --check '**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
"format:write": "prettier --write '**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}' && sql-formatter -l postgresql ./database/query.sql -o ./database/query.sql",
"format:write": "prettier --write '**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
"lint": "jest --selectProjects lint",
"lint:fix": "FIX=true yarn lint",
"playwright:install": "playwright install",

View File

@ -6,9 +6,9 @@ import { NotFoundPage } from "./pages/404"
import { CliAuthenticationPage } from "./pages/cli-auth"
import { HealthzPage } from "./pages/healthz"
import { SignInPage } from "./pages/login"
import { ProjectsPage } from "./pages/projects"
import { ProjectPage } from "./pages/projects/[organization]/[project]"
import { CreateWorkspacePage } from "./pages/projects/[organization]/[project]/create"
import { TemplatesPage } from "./pages/templates"
import { TemplatePage } from "./pages/templates/[organization]/[template]"
import { CreateWorkspacePage } from "./pages/templates/[organization]/[template]/create"
import { WorkspacePage } from "./pages/workspaces/[workspace]"
export const AppRouter: React.FC = () => (
@ -27,21 +27,21 @@ export const AppRouter: React.FC = () => (
<Route path="healthz" element={<HealthzPage />} />
<Route path="cli-auth" element={<CliAuthenticationPage />} />
<Route path="projects">
<Route path="templates">
<Route
index
element={
<AuthAndNav>
<ProjectsPage />
<TemplatesPage />
</AuthAndNav>
}
/>
<Route path=":organization/:project">
<Route path=":organization/:template">
<Route
index
element={
<AuthAndNav>
<ProjectPage />
<TemplatePage />
</AuthAndNav>
}
/>

View File

@ -32,11 +32,11 @@ export namespace Workspace {
throw new Error(body.message)
}
// Let SWR know that both the /api/v2/workspaces/* and /api/v2/projects/*
// Let SWR know that both the /api/v2/workspaces/* and /api/v2/templates/*
// endpoints will need to fetch new data.
const mutateWorkspacesPromise = mutate("/api/v2/workspaces")
const mutateProjectsPromise = mutate("/api/v2/projects")
await Promise.all([mutateWorkspacesPromise, mutateProjectsPromise])
const mutateTemplatesPromise = mutate("/api/v2/templates")
await Promise.all([mutateWorkspacesPromise, mutateTemplatesPromise])
return body
}

View File

@ -24,8 +24,8 @@ export interface Provisioner {
name: string
}
// This must be kept in sync with the `Project` struct in the back-end
export interface Project {
// This must be kept in sync with the `Template` struct in the back-end
export interface Template {
id: string
created_at: string
updated_at: string
@ -35,7 +35,7 @@ export interface Project {
active_version_id: string
}
export interface CreateProjectRequest {
export interface CreateTemplateRequest {
name: string
organizationId: string
provisioner: string
@ -43,7 +43,7 @@ export interface CreateProjectRequest {
export interface CreateWorkspaceRequest {
name: string
project_id: string
template_id: string
}
// Must be kept in sync with backend Workspace struct
@ -52,7 +52,7 @@ export interface Workspace {
created_at: string
updated_at: string
owner_id: string
project_id: string
template_id: string
name: string
}

View File

@ -1,6 +1,6 @@
import { Story } from "@storybook/react"
import React from "react"
import { MockOrganization, MockProject, MockWorkspace } from "../../test_helpers"
import { MockOrganization, MockTemplate, MockWorkspace } from "../../test_helpers"
import { Workspace, WorkspaceProps } from "./Workspace"
export default {
@ -14,6 +14,6 @@ const Template: Story<WorkspaceProps> = (args) => <Workspace {...args} />
export const Example = Template.bind({})
Example.args = {
organization: MockOrganization,
project: MockProject,
template: MockTemplate,
workspace: MockWorkspace,
}

View File

@ -1,12 +1,12 @@
import { screen } from "@testing-library/react"
import React from "react"
import { MockOrganization, MockProject, MockWorkspace, render } from "../../test_helpers"
import { MockOrganization, MockTemplate, MockWorkspace, render } from "../../test_helpers"
import { Workspace } from "./Workspace"
describe("Workspace", () => {
it("renders", async () => {
// When
render(<Workspace organization={MockOrganization} project={MockProject} workspace={MockWorkspace} />)
render(<Workspace organization={MockOrganization} template={MockTemplate} workspace={MockWorkspace} />)
// Then
const element = await screen.findByText(MockWorkspace.name)

View File

@ -12,19 +12,19 @@ import { WorkspaceSection } from "./WorkspaceSection"
export interface WorkspaceProps {
organization: Types.Organization
workspace: Types.Workspace
project: Types.Project
template: Types.Template
}
/**
* Workspace is the top-level component for viewing an individual workspace
*/
export const Workspace: React.FC<WorkspaceProps> = ({ organization, project, workspace }) => {
export const Workspace: React.FC<WorkspaceProps> = ({ organization, template, workspace }) => {
const styles = useStyles()
return (
<div className={styles.root}>
<div className={styles.vertical}>
<WorkspaceHeader organization={organization} project={project} workspace={workspace} />
<WorkspaceHeader organization={organization} template={template} workspace={workspace} />
<div className={styles.horizontal}>
<div className={styles.sidebarContainer}>
<WorkspaceSection title="Applications">
@ -56,10 +56,10 @@ export const Workspace: React.FC<WorkspaceProps> = ({ organization, project, wor
/**
* Component for the header at the top of the workspace page
*/
export const WorkspaceHeader: React.FC<WorkspaceProps> = ({ organization, project, workspace }) => {
export const WorkspaceHeader: React.FC<WorkspaceProps> = ({ organization, template, workspace }) => {
const styles = useStyles()
const projectLink = `/projects/${organization.name}/${project.name}`
const templateLink = `/templates/${organization.name}/${template.name}`
return (
<Paper elevation={0} className={styles.section}>
@ -68,7 +68,7 @@ export const WorkspaceHeader: React.FC<WorkspaceProps> = ({ organization, projec
<div className={styles.vertical}>
<Typography variant="h4">{workspace.name}</Typography>
<Typography variant="body2" color="textSecondary">
<Link to={projectLink}>{project.name}</Link>
<Link to={templateLink}>{template.name}</Link>
</Typography>
</div>
</div>

View File

@ -1,19 +1,19 @@
import { render, screen } from "@testing-library/react"
import React from "react"
import { MockOrganization, MockProject, MockProvisioner } from "./../test_helpers"
import { CreateProjectForm } from "./CreateProjectForm"
import { MockOrganization, MockProvisioner, MockTemplate } from "./../test_helpers"
import { CreateTemplateForm } from "./CreateTemplateForm"
describe("CreateProjectForm", () => {
describe("CreateTemplateForm", () => {
it("renders", async () => {
// Given
const provisioners = [MockProvisioner]
const organizations = [MockOrganization]
const onSubmit = () => Promise.resolve(MockProject)
const onSubmit = () => Promise.resolve(MockTemplate)
const onCancel = () => Promise.resolve()
// When
render(
<CreateProjectForm
<CreateTemplateForm
provisioners={provisioners}
organizations={organizations}
onSubmit={onSubmit}
@ -23,7 +23,7 @@ describe("CreateProjectForm", () => {
// Then
// Simple smoke test to verify form renders
const element = await screen.findByText("Create Project")
const element = await screen.findByText("Create Template")
expect(element).toBeDefined()
})
})

View File

@ -3,7 +3,7 @@ import { makeStyles } from "@material-ui/core/styles"
import { FormikContextType, useFormik } from "formik"
import React from "react"
import * as Yup from "yup"
import { CreateProjectRequest, Organization, Project, Provisioner } from "../api/types"
import { CreateTemplateRequest, Organization, Provisioner, Template } from "../api/types"
import { LoadingButton } from "../components/Button"
import {
DropdownItem,
@ -14,10 +14,10 @@ import {
FormTitle,
} from "../components/Form"
export interface CreateProjectFormProps {
export interface CreateTemplateFormProps {
provisioners: Provisioner[]
organizations: Organization[]
onSubmit: (request: CreateProjectRequest) => Promise<Project>
onSubmit: (request: CreateTemplateRequest) => Promise<Template>
onCancel: () => void
}
@ -27,7 +27,7 @@ const validationSchema = Yup.object({
name: Yup.string().required("Name is required"),
})
export const CreateProjectForm: React.FC<CreateProjectFormProps> = ({
export const CreateTemplateForm: React.FC<CreateTemplateFormProps> = ({
provisioners,
organizations,
onSubmit,
@ -35,7 +35,7 @@ export const CreateProjectForm: React.FC<CreateProjectFormProps> = ({
}) => {
const styles = useStyles()
const form: FormikContextType<CreateProjectRequest> = useFormik<CreateProjectRequest>({
const form: FormikContextType<CreateTemplateRequest> = useFormik<CreateTemplateRequest>({
initialValues: {
provisioner: provisioners[0].id,
organizationId: organizations[0].name,
@ -64,7 +64,7 @@ export const CreateProjectForm: React.FC<CreateProjectFormProps> = ({
return (
<div className={styles.root}>
<FormTitle title="Create Project" />
<FormTitle title="Create Template" />
<FormCloseButton onClose={onCancel} />
<FormSection title="Name">
@ -72,9 +72,9 @@ export const CreateProjectForm: React.FC<CreateProjectFormProps> = ({
form={form}
formFieldName="name"
fullWidth
helperText="A unique name describing your project."
label="Project Name"
placeholder="my-project"
helperText="A unique name describing your template."
label="Template Name"
placeholder="my-template"
required
/>
</FormSection>
@ -83,7 +83,7 @@ export const CreateProjectForm: React.FC<CreateProjectFormProps> = ({
<FormDropdownField
form={form}
formFieldName="organizationId"
helperText="The organization owning this project."
helperText="The organization owning this template."
items={organizationDropDownItems}
fullWidth
select
@ -95,7 +95,7 @@ export const CreateProjectForm: React.FC<CreateProjectFormProps> = ({
<FormDropdownField
form={form}
formFieldName="provisioner"
helperText="The backing provisioner for this project."
helperText="The backing provisioner for this template."
items={provisionerDropDownItems}
fullWidth
select

Some files were not shown because too many files have changed in this diff Show More