Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

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

Provide feedback

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

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: run a terraform plan before creating workspaces with the given template parameters#1732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
deansheather merged 11 commits intomainfromtemplate-version-plan
Jun 1, 2022
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletionscli/cliui/provisionerjob.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
package cliui

import (
"bytes"
"context"
"fmt"
"io"
Expand DownExpand Up@@ -35,6 +36,9 @@ type ProvisionerJobOptions struct {
FetchInterval time.Duration
// Verbose determines whether debug and trace logs will be shown.
Verbose bool
// Silent determines whether log output will be shown unless there is an
// error.
Silent bool
}

// ProvisionerJob renders a provisioner job with interactive cancellation.
Expand DownExpand Up@@ -133,12 +137,30 @@ func ProvisionerJob(ctx context.Context, writer io.Writer, opts ProvisionerJobOp
return xerrors.Errorf("logs: %w", err)
}

var (
// logOutput is where log output is written
logOutput = writer
// logBuffer is where logs are buffered if opts.Silent is true
logBuffer = &bytes.Buffer{}
)
if opts.Silent {
logOutput = logBuffer
}
flushLogBuffer := func() {
if opts.Silent {
_, _ = io.Copy(writer, logBuffer)
}
}

ticker := time.NewTicker(opts.FetchInterval)
defer ticker.Stop()
for {
select {
case err = <-errChan:
flushLogBuffer()
return err
case <-ctx.Done():
flushLogBuffer()
return ctx.Err()
case <-ticker.C:
updateJob()
Expand All@@ -160,8 +182,10 @@ func ProvisionerJob(ctx context.Context, writer io.Writer, opts ProvisionerJobOp
}
err = xerrors.New(job.Error)
jobMutex.Unlock()
flushLogBuffer()
return err
}

output := ""
switch log.Level {
case codersdk.LogLevelTrace, codersdk.LogLevelDebug:
Expand All@@ -176,14 +200,17 @@ func ProvisionerJob(ctx context.Context, writer io.Writer, opts ProvisionerJobOp
case codersdk.LogLevelInfo:
output = log.Output
}

jobMutex.Lock()
if log.Stage != currentStage && log.Stage != "" {
updateStage(log.Stage, log.CreatedAt)
jobMutex.Unlock()
continue
}
_, _ = fmt.Fprintf(writer, "%s %s\n", Styles.Placeholder.Render(" "), output)
didLogBetweenStage = true
_, _ = fmt.Fprintf(logOutput, "%s %s\n", Styles.Placeholder.Render(" "), output)
if !opts.Silent {
didLogBetweenStage = true
}
jobMutex.Unlock()
}
}
Expand Down
37 changes: 33 additions & 4 deletionscli/create.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -170,10 +170,40 @@ func create() *cobra.Command {
}
_, _ = fmt.Fprintln(cmd.OutOrStdout())

resources, err := client.TemplateVersionResources(cmd.Context(), templateVersion.ID)
// Run a dry-run with the given parameters to check correctness
after := time.Now()
dryRun, err := client.CreateTemplateVersionDryRun(cmd.Context(), templateVersion.ID, codersdk.CreateTemplateVersionDryRunRequest{
WorkspaceName: workspaceName,
ParameterValues: parameters,
})
if err != nil {
return err
return xerrors.Errorf("begin workspace dry-run: %w", err)
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Planning workspace...")
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I kept the language "Planning workspace" because I couldn't come up with something that made sense with the word "dry-run"....

"Dry-running workspace"

"Running workspace dry-run"

etc.

err = cliui.ProvisionerJob(cmd.Context(), cmd.OutOrStdout(), cliui.ProvisionerJobOptions{
Fetch: func() (codersdk.ProvisionerJob, error) {
return client.TemplateVersionDryRun(cmd.Context(), templateVersion.ID, dryRun.ID)
},
Cancel: func() error {
return client.CancelTemplateVersionDryRun(cmd.Context(), templateVersion.ID, dryRun.ID)
},
Logs: func() (<-chan codersdk.ProvisionerJobLog, error) {
return client.TemplateVersionDryRunLogsAfter(cmd.Context(), templateVersion.ID, dryRun.ID, after)
},
// Don't show log output for the dry-run unless there's an error.
Silent: true,
})
if err != nil {
// TODO (Dean): reprompt for parameter values if we deem it to
// be a validation error
Comment on lines +197 to +198
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

TODO: open an issue

kylecarbs and deansheather reacted with thumbs up emoji
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Opened two issues:

#1872 - for integrating with frontend
#1873 - for reprompting for values in CLI

johnstcn reacted with heart emoji
return xerrors.Errorf("dry-run workspace: %w", err)
}

resources, err := client.TemplateVersionDryRunResources(cmd.Context(), templateVersion.ID, dryRun.ID)
if err != nil {
return xerrors.Errorf("get workspace dry-run resources: %w", err)
}

err = cliui.WorkspaceResources(cmd.OutOrStdout(), resources, cliui.WorkspaceResourcesOptions{
WorkspaceName: workspaceName,
// Since agent's haven't connected yet, hiding this makes more sense.
Expand All@@ -192,7 +222,6 @@ func create() *cobra.Command {
return err
}

before := time.Now()
workspace, err := client.CreateWorkspace(cmd.Context(), organization.ID, codersdk.CreateWorkspaceRequest{
TemplateID: template.ID,
Name: workspaceName,
Expand All@@ -204,7 +233,7 @@ func create() *cobra.Command {
return err
}

err = cliui.WorkspaceBuild(cmd.Context(), cmd.OutOrStdout(), client, workspace.LatestBuild.ID,before)
err = cliui.WorkspaceBuild(cmd.Context(), cmd.OutOrStdout(), client, workspace.LatestBuild.ID,after)
if err != nil {
return err
}
Expand Down
48 changes: 48 additions & 0 deletionscli/create_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ package cli_test

import (
"context"
"database/sql"
"fmt"
"os"
"testing"
Expand All@@ -12,6 +13,8 @@ import (

"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/provisionersdk/proto"
"github.com/coder/coder/pty/ptytest"
Expand DownExpand Up@@ -249,6 +252,7 @@ func TestCreate(t *testing.T) {
<-doneChan
removeTmpDirUntilSuccess(t, tempDir)
})

t.Run("WithParameterFileNotContainingTheValue", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
Expand DownExpand Up@@ -279,6 +283,50 @@ func TestCreate(t *testing.T) {
<-doneChan
removeTmpDirUntilSuccess(t, tempDir)
})

t.Run("FailedDryRun", func(t *testing.T) {
t.Parallel()
client, api := coderdtest.NewWithAPI(t, &coderdtest.Options{IncludeProvisionerD: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionDryRun: []*proto.Provision_Response{
{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Error: "test error",
},
},
},
},
})

// The template import job should end up failed, but we need it to be
// succeeded so the dry-run can begin.
version = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
require.Equal(t, codersdk.ProvisionerJobFailed, version.Job.Status, "job is not failed")
err := api.Database.UpdateProvisionerJobWithCompleteByID(context.Background(), database.UpdateProvisionerJobWithCompleteByIDParams{
ID: version.Job.ID,
CompletedAt: sql.NullTime{
Time: time.Now(),
Valid: true,
},
UpdatedAt: time.Now(),
Error: sql.NullString{},
})
require.NoError(t, err, "update provisioner job")

_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
cmd, root := clitest.New(t, "create", "test")
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())

err = cmd.Execute()
require.Error(t, err)
require.ErrorContains(t, err, "dry-run workspace")
})
}

func createTestParseResponseWithDefault(defaultValue string) []*proto.Parse_Response {
Expand Down
7 changes: 7 additions & 0 deletionscoderd/coderd.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -207,6 +207,13 @@ func New(options *Options) *API {
r.Get("/parameters", api.templateVersionParameters)
r.Get("/resources", api.templateVersionResources)
r.Get("/logs", api.templateVersionLogs)
r.Route("/dry-run", func(r chi.Router) {
r.Post("/", api.postTemplateVersionDryRun)
r.Get("/{jobID}", api.templateVersionDryRun)
r.Get("/{jobID}/resources", api.templateVersionDryRunResources)
r.Get("/{jobID}/logs", api.templateVersionDryRunLogs)
r.Patch("/{jobID}/cancel", api.patchTemplateVersionDryRunCancel)
})
})
r.Route("/users", func(r chi.Router) {
r.Get("/first", api.firstUser)
Expand Down
26 changes: 26 additions & 0 deletionscoderd/coderd_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,6 +96,10 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
require.NoError(t, err, "upload file")
workspaceResources, err := client.WorkspaceResourcesByBuild(ctx, workspace.LatestBuild.ID)
require.NoError(t, err, "workspace resources")
templateVersionDryRun, err := client.CreateTemplateVersionDryRun(ctx, version.ID, codersdk.CreateTemplateVersionDryRunRequest{
ParameterValues: []codersdk.CreateParameterRequest{},
})
require.NoError(t, err, "template version dry-run")

// Always fail auth from this point forward
authorizer.AlwaysReturn = rbac.ForbiddenWithInternal(xerrors.New("fake implementation"), nil, nil)
Expand DownExpand Up@@ -264,6 +268,27 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
AssertAction: rbac.ActionRead,
AssertObject: rbac.ResourceTemplate.InOrg(template.OrganizationID).WithID(template.ID.String()),
},
"POST:/api/v2/templateversions/{templateversion}/dry-run": {
// The first check is to read the template
AssertAction: rbac.ActionRead,
AssertObject: rbac.ResourceTemplate.InOrg(version.OrganizationID).WithID(template.ID.String()),
},
"GET:/api/v2/templateversions/{templateversion}/dry-run/{templateversiondryrun}": {
AssertAction: rbac.ActionRead,
AssertObject: rbac.ResourceTemplate.InOrg(version.OrganizationID).WithID(template.ID.String()),
},
"GET:/api/v2/templateversions/{templateversion}/dry-run/{templateversiondryrun}/resources": {
AssertAction: rbac.ActionRead,
AssertObject: rbac.ResourceTemplate.InOrg(version.OrganizationID).WithID(template.ID.String()),
},
"GET:/api/v2/templateversions/{templateversion}/dry-run/{templateversiondryrun}/logs": {
AssertAction: rbac.ActionRead,
AssertObject: rbac.ResourceTemplate.InOrg(version.OrganizationID).WithID(template.ID.String()),
},
"PATCH:/api/v2/templateversions/{templateversion}/dry-run/{templateversiondryrun}/cancel": {
AssertAction: rbac.ActionRead,
AssertObject: rbac.ResourceTemplate.InOrg(version.OrganizationID).WithID(template.ID.String()),
},
"GET:/api/v2/provisionerdaemons": {
StatusCode: http.StatusOK,
AssertObject: rbac.ResourceProvisionerDaemon.WithID(provisionerds[0].ID.String()),
Expand DownExpand Up@@ -326,6 +351,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
route = strings.ReplaceAll(route, "{hash}", file.Hash)
route = strings.ReplaceAll(route, "{workspaceresource}", workspaceResources[0].ID.String())
route = strings.ReplaceAll(route, "{templateversion}", version.ID.String())
route = strings.ReplaceAll(route, "{templateversiondryrun}", templateVersionDryRun.ID.String())
route = strings.ReplaceAll(route, "{templatename}", template.Name)
// Only checking org scoped params here
route = strings.ReplaceAll(route, "{scope}", string(organizationParam.Scope))
Expand Down
3 changes: 2 additions & 1 deletioncoderd/database/dump.sql
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
-- It's not possible to drop enum values from enum types, so the UP has "IF NOT
-- EXISTS".

-- Delete all jobs that use the new enum value.
DELETE FROM
provisioner_jobs
WHERE
type = 'template_version_dry_run'
;
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
ALTER TYPE provisioner_job_type
ADD VALUE IF NOT EXISTS 'template_version_dry_run';
1 change: 1 addition & 0 deletionscoderd/database/models.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

19 changes: 14 additions & 5 deletionscoderd/parameter/compute.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,11 +13,12 @@ import (

// ComputeScope targets identifiers to pull parameters from.
type ComputeScope struct {
TemplateImportJobID uuid.UUID
OrganizationID uuid.UUID
UserID uuid.UUID
TemplateID uuid.NullUUID
WorkspaceID uuid.NullUUID
TemplateImportJobID uuid.UUID
OrganizationID uuid.UUID
UserID uuid.UUID
TemplateID uuid.NullUUID
WorkspaceID uuid.NullUUID
AdditionalParameterValues []database.ParameterValue
}

type ComputeOptions struct {
Expand DownExpand Up@@ -142,6 +143,14 @@ func Compute(ctx context.Context, db database.Store, scope ComputeScope, options
}
}

// Finally, any additional parameter values declared in the input
for _, v := range scope.AdditionalParameterValues {
err = compute.injectSingle(v, false)
if err != nil {
return nil, xerrors.Errorf("inject single parameter value: %w", err)
}
}

values := make([]ComputedValue, 0, len(compute.computedParameterByName))
for _, value := range compute.computedParameterByName {
values = append(values, value)
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp