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,
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: