diff --git a/coderd/agentapi/apps.go b/coderd/agentapi/apps.go index 956e154e89d0d..89c1a873d6310 100644 --- a/coderd/agentapi/apps.go +++ b/coderd/agentapi/apps.go @@ -92,7 +92,7 @@ func (a *AppsAPI) BatchUpdateAppHealths(ctx context.Context, req *agentproto.Bat Health: app.Health, }) if err != nil { - return nil, xerrors.Errorf("update workspace app health for app %q (%q): %w", err, app.ID, app.Slug) + return nil, xerrors.Errorf("update workspace app health for app %q (%q): %w", app.ID, app.Slug, err) } } diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 78dcd4e993b9f..a8f7c63a586fe 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -2595,8 +2595,19 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. openIn = database.WorkspaceAppOpenInSlimWindow } + var appID string + if app.Id == "" || app.Id == uuid.Nil.String() { + appID = uuid.NewString() + } else { + appID = app.Id + } + id, err := uuid.Parse(appID) + if err != nil { + return xerrors.Errorf("parse app uuid: %w", err) + } + dbApp, err := db.InsertWorkspaceApp(ctx, database.InsertWorkspaceAppParams{ - ID: uuid.New(), + ID: id, CreatedAt: dbtime.Now(), AgentID: dbAgent.ID, Slug: slug, diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index 686a947f7fcaa..a46953f030dda 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/awalterschulze/gographviz" + "github.com/google/uuid" tfjson "github.com/hashicorp/terraform-json" "github.com/mitchellh/mapstructure" "golang.org/x/xerrors" @@ -93,6 +94,7 @@ type agentDisplayAppsAttributes struct { // A mapping of attributes on the "coder_app" resource. type agentAppAttributes struct { + ID string `mapstructure:"id"` AgentID string `mapstructure:"agent_id"` // Slug is required in terraform, but to avoid breaking existing users we // will default to the resource name if it is not specified. @@ -522,7 +524,17 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s continue } + id := attrs.ID + if id == "" { + // This should never happen since the "id" attribute is set on creation: + // https://github.com/coder/terraform-provider-coder/blob/cfa101df4635e405e66094fa7779f9a89d92f400/provider/app.go#L37 + logger.Warn(ctx, "coder_app's id was unexpectedly empty", slog.F("name", attrs.Name)) + + id = uuid.NewString() + } + agent.Apps = append(agent.Apps, &proto.App{ + Id: id, Slug: attrs.Slug, DisplayName: attrs.DisplayName, Command: attrs.Command, diff --git a/provisioner/terraform/resources_test.go b/provisioner/terraform/resources_test.go index d21c0a9c573ff..56077eed9eee9 100644 --- a/provisioner/terraform/resources_test.go +++ b/provisioner/terraform/resources_test.go @@ -967,6 +967,9 @@ func TestConvertResources(t *testing.T) { if agent.GetInstanceId() != "" { agent.Auth = &proto.Agent_InstanceId{} } + for _, app := range agent.Apps { + app.Id = "" + } } } @@ -1037,6 +1040,9 @@ func TestConvertResources(t *testing.T) { if agent.GetInstanceId() != "" { agent.Auth = &proto.Agent_InstanceId{} } + for _, app := range agent.Apps { + app.Id = "" + } } } // Convert expectedNoMetadata and resources into a diff --git a/provisionerd/proto/version.go b/provisionerd/proto/version.go index d5ecba99030b3..3ac93d7497860 100644 --- a/provisionerd/proto/version.go +++ b/provisionerd/proto/version.go @@ -37,6 +37,7 @@ import "github.com/coder/coder/v2/apiversion" // - Add new field named `scheduling` to `Prebuild`, with fields for timezone // and schedule rules to define cron-based scaling of prebuilt workspace // instances based on time patterns. +// - Added new field named `id` to `App`, which transports the ID generated by the coder_app provider to be persisted. const ( CurrentMajor = 1 CurrentMinor = 7 diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index 81ca588efaf93..047458457fedd 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -2285,6 +2285,7 @@ type App struct { Hidden bool `protobuf:"varint,11,opt,name=hidden,proto3" json:"hidden,omitempty"` OpenIn AppOpenIn `protobuf:"varint,12,opt,name=open_in,json=openIn,proto3,enum=provisioner.AppOpenIn" json:"open_in,omitempty"` Group string `protobuf:"bytes,13,opt,name=group,proto3" json:"group,omitempty"` + Id string `protobuf:"bytes,14,opt,name=id,proto3" json:"id,omitempty"` // If nil, new UUID will be generated. } func (x *App) Reset() { @@ -2410,6 +2411,13 @@ func (x *App) GetGroup() string { return "" } +func (x *App) GetId() string { + if x != nil { + return x.Id + } + return "" +} + // Healthcheck represents configuration for checking for app readiness. type Healthcheck struct { state protoimpl.MessageState @@ -4474,7 +4482,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, - 0xaa, 0x03, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, + 0xba, 0x03, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, @@ -4500,7 +4508,8 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x52, 0x06, 0x6f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, - 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x59, 0x0a, 0x0b, + 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index cd4eb4960eb11..12dd709aeba7f 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -266,6 +266,7 @@ message App { bool hidden = 11; AppOpenIn open_in = 12; string group = 13; + string id = 14; // If nil, new UUID will be generated. } // Healthcheck represents configuration for checking for app readiness. diff --git a/site/e2e/provisionerGenerated.ts b/site/e2e/provisionerGenerated.ts index ee53f18d66d58..9b6a0b3109ef7 100644 --- a/site/e2e/provisionerGenerated.ts +++ b/site/e2e/provisionerGenerated.ts @@ -311,6 +311,8 @@ export interface App { hidden: boolean; openIn: AppOpenIn; group: string; + /** If nil, new UUID will be generated. */ + id: string; } /** Healthcheck represents configuration for checking for app readiness. */ @@ -1041,6 +1043,9 @@ export const App = { if (message.group !== "") { writer.uint32(106).string(message.group); } + if (message.id !== "") { + writer.uint32(114).string(message.id); + } return writer; }, }; diff --git a/site/e2e/tests/app.spec.ts b/site/e2e/tests/app.spec.ts index a12c1baccd735..587775b4dc3f8 100644 --- a/site/e2e/tests/app.spec.ts +++ b/site/e2e/tests/app.spec.ts @@ -42,6 +42,7 @@ test("app", async ({ context, page }) => { token, apps: [ { + id: randomUUID(), url: `http://localhost:${addr.port}`, displayName: appName, order: 0,