mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
feat: remove agent name from app URLs (#19750)
## Summary In this pull request we're removing `agent_name` from subdomains in APP urls when an `app` is used in the subdomain. `agent_names` will still be used when a `port` is used in the subdomain. Closes: https://github.com/coder/coder/issues/18485 ### Changes - Updated regex to support an optional agent name - Added logic to support checking the app slug for a matching port (e.g., 8080 or 8080s) ### Testing - Updated all tests to support an optional `agent_name`
This commit is contained in:
@@ -110,7 +110,7 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
|
||||
}
|
||||
}
|
||||
|
||||
apps, err := dbAppsToProto(dbApps, workspaceAgent, workspace.OwnerUsername, workspace)
|
||||
apps, err := dbAppsToProto(dbApps, workspaceAgent, workspace.OwnerUsername, workspace, a.AppHostname)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("converting workspace apps: %w", err)
|
||||
}
|
||||
@@ -196,11 +196,11 @@ func dbAgentScriptToProto(script database.WorkspaceAgentScript) *agentproto.Work
|
||||
}
|
||||
}
|
||||
|
||||
func dbAppsToProto(dbApps []database.WorkspaceApp, agent database.WorkspaceAgent, ownerName string, workspace database.Workspace) ([]*agentproto.WorkspaceApp, error) {
|
||||
func dbAppsToProto(dbApps []database.WorkspaceApp, agent database.WorkspaceAgent, ownerName string, workspace database.Workspace, appHostname string) ([]*agentproto.WorkspaceApp, error) {
|
||||
ret := make([]*agentproto.WorkspaceApp, len(dbApps))
|
||||
for i, dbApp := range dbApps {
|
||||
var err error
|
||||
ret[i], err = dbAppToProto(dbApp, agent, ownerName, workspace)
|
||||
ret[i], err = dbAppToProto(dbApp, agent, ownerName, workspace, appHostname)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse app %v (%q): %w", i, dbApp.Slug, err)
|
||||
}
|
||||
@@ -208,7 +208,7 @@ func dbAppsToProto(dbApps []database.WorkspaceApp, agent database.WorkspaceAgent
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func dbAppToProto(dbApp database.WorkspaceApp, agent database.WorkspaceAgent, ownerName string, workspace database.Workspace) (*agentproto.WorkspaceApp, error) {
|
||||
func dbAppToProto(dbApp database.WorkspaceApp, agent database.WorkspaceAgent, ownerName string, workspace database.Workspace, appHostname string) (*agentproto.WorkspaceApp, error) {
|
||||
sharingLevelRaw, ok := agentproto.WorkspaceApp_SharingLevel_value[strings.ToUpper(string(dbApp.SharingLevel))]
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("unknown app sharing level: %q", dbApp.SharingLevel)
|
||||
@@ -219,6 +219,12 @@ func dbAppToProto(dbApp database.WorkspaceApp, agent database.WorkspaceAgent, ow
|
||||
return nil, xerrors.Errorf("unknown app health: %q", dbApp.SharingLevel)
|
||||
}
|
||||
|
||||
// SubdomainName should be empty if AppHostname is not configured
|
||||
subdomainName := ""
|
||||
if appHostname != "" {
|
||||
subdomainName = db2sdk.AppSubdomain(dbApp, agent.Name, workspace.Name, ownerName)
|
||||
}
|
||||
|
||||
return &agentproto.WorkspaceApp{
|
||||
Id: dbApp.ID[:],
|
||||
Url: dbApp.Url.String,
|
||||
@@ -228,7 +234,7 @@ func dbAppToProto(dbApp database.WorkspaceApp, agent database.WorkspaceAgent, ow
|
||||
Command: dbApp.Command.String,
|
||||
Icon: dbApp.Icon,
|
||||
Subdomain: dbApp.Subdomain,
|
||||
SubdomainName: db2sdk.AppSubdomain(dbApp, agent.Name, workspace.Name, ownerName),
|
||||
SubdomainName: subdomainName,
|
||||
SharingLevel: agentproto.WorkspaceApp_SharingLevel(sharingLevelRaw),
|
||||
Healthcheck: &agentproto.WorkspaceApp_Healthcheck{
|
||||
Url: dbApp.HealthcheckUrl,
|
||||
|
||||
@@ -191,65 +191,6 @@ func TestGetManifest(t *testing.T) {
|
||||
// These are done manually to ensure the conversion logic matches what a
|
||||
// human expects.
|
||||
var (
|
||||
protoApps = []*agentproto.WorkspaceApp{
|
||||
{
|
||||
Id: apps[0].ID[:],
|
||||
Url: apps[0].Url.String,
|
||||
External: apps[0].External,
|
||||
Slug: apps[0].Slug,
|
||||
DisplayName: apps[0].DisplayName,
|
||||
Command: apps[0].Command.String,
|
||||
Icon: apps[0].Icon,
|
||||
Subdomain: apps[0].Subdomain,
|
||||
SubdomainName: fmt.Sprintf("%s--%s--%s--%s", apps[0].Slug, agent.Name, workspace.Name, owner.Username),
|
||||
SharingLevel: agentproto.WorkspaceApp_AUTHENTICATED,
|
||||
Healthcheck: &agentproto.WorkspaceApp_Healthcheck{
|
||||
Url: apps[0].HealthcheckUrl,
|
||||
Interval: durationpb.New(time.Duration(apps[0].HealthcheckInterval) * time.Second),
|
||||
Threshold: apps[0].HealthcheckThreshold,
|
||||
},
|
||||
Health: agentproto.WorkspaceApp_HEALTHY,
|
||||
Hidden: false,
|
||||
},
|
||||
{
|
||||
Id: apps[1].ID[:],
|
||||
Url: apps[1].Url.String,
|
||||
External: apps[1].External,
|
||||
Slug: apps[1].Slug,
|
||||
DisplayName: apps[1].DisplayName,
|
||||
Command: apps[1].Command.String,
|
||||
Icon: apps[1].Icon,
|
||||
Subdomain: false,
|
||||
SubdomainName: "",
|
||||
SharingLevel: agentproto.WorkspaceApp_PUBLIC,
|
||||
Healthcheck: &agentproto.WorkspaceApp_Healthcheck{
|
||||
Url: "",
|
||||
Interval: durationpb.New(0),
|
||||
Threshold: 0,
|
||||
},
|
||||
Health: agentproto.WorkspaceApp_DISABLED,
|
||||
Hidden: false,
|
||||
},
|
||||
{
|
||||
Id: apps[2].ID[:],
|
||||
Url: apps[2].Url.String,
|
||||
External: apps[2].External,
|
||||
Slug: apps[2].Slug,
|
||||
DisplayName: apps[2].DisplayName,
|
||||
Command: apps[2].Command.String,
|
||||
Icon: apps[2].Icon,
|
||||
Subdomain: false,
|
||||
SubdomainName: "",
|
||||
SharingLevel: agentproto.WorkspaceApp_OWNER,
|
||||
Healthcheck: &agentproto.WorkspaceApp_Healthcheck{
|
||||
Url: apps[2].HealthcheckUrl,
|
||||
Interval: durationpb.New(time.Duration(apps[2].HealthcheckInterval) * time.Second),
|
||||
Threshold: apps[2].HealthcheckThreshold,
|
||||
},
|
||||
Health: agentproto.WorkspaceApp_UNHEALTHY,
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
protoScripts = []*agentproto.WorkspaceAgentScript{
|
||||
{
|
||||
Id: scripts[0].ID[:],
|
||||
@@ -308,6 +249,66 @@ func TestGetManifest(t *testing.T) {
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
protoApps := []*agentproto.WorkspaceApp{
|
||||
{
|
||||
Id: apps[0].ID[:],
|
||||
Url: apps[0].Url.String,
|
||||
External: apps[0].External,
|
||||
Slug: apps[0].Slug,
|
||||
DisplayName: apps[0].DisplayName,
|
||||
Command: apps[0].Command.String,
|
||||
Icon: apps[0].Icon,
|
||||
Subdomain: apps[0].Subdomain,
|
||||
SubdomainName: fmt.Sprintf("%s--%s--%s", apps[0].Slug, workspace.Name, owner.Username),
|
||||
SharingLevel: agentproto.WorkspaceApp_AUTHENTICATED,
|
||||
Healthcheck: &agentproto.WorkspaceApp_Healthcheck{
|
||||
Url: apps[0].HealthcheckUrl,
|
||||
Interval: durationpb.New(time.Duration(apps[0].HealthcheckInterval) * time.Second),
|
||||
Threshold: apps[0].HealthcheckThreshold,
|
||||
},
|
||||
Health: agentproto.WorkspaceApp_HEALTHY,
|
||||
Hidden: false,
|
||||
},
|
||||
{
|
||||
Id: apps[1].ID[:],
|
||||
Url: apps[1].Url.String,
|
||||
External: apps[1].External,
|
||||
Slug: apps[1].Slug,
|
||||
DisplayName: apps[1].DisplayName,
|
||||
Command: apps[1].Command.String,
|
||||
Icon: apps[1].Icon,
|
||||
Subdomain: false,
|
||||
SubdomainName: "",
|
||||
SharingLevel: agentproto.WorkspaceApp_PUBLIC,
|
||||
Healthcheck: &agentproto.WorkspaceApp_Healthcheck{
|
||||
Url: "",
|
||||
Interval: durationpb.New(0),
|
||||
Threshold: 0,
|
||||
},
|
||||
Health: agentproto.WorkspaceApp_DISABLED,
|
||||
Hidden: false,
|
||||
},
|
||||
{
|
||||
Id: apps[2].ID[:],
|
||||
Url: apps[2].Url.String,
|
||||
External: apps[2].External,
|
||||
Slug: apps[2].Slug,
|
||||
DisplayName: apps[2].DisplayName,
|
||||
Command: apps[2].Command.String,
|
||||
Icon: apps[2].Icon,
|
||||
Subdomain: false,
|
||||
SubdomainName: "",
|
||||
SharingLevel: agentproto.WorkspaceApp_OWNER,
|
||||
Healthcheck: &agentproto.WorkspaceApp_Healthcheck{
|
||||
Url: apps[2].HealthcheckUrl,
|
||||
Interval: durationpb.New(time.Duration(apps[2].HealthcheckInterval) * time.Second),
|
||||
Threshold: apps[2].HealthcheckThreshold,
|
||||
},
|
||||
Health: agentproto.WorkspaceApp_UNHEALTHY,
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
|
||||
mDB := dbmock.NewMockStore(gomock.NewController(t))
|
||||
|
||||
api := &agentapi.ManifestAPI{
|
||||
@@ -438,6 +439,66 @@ func TestGetManifest(t *testing.T) {
|
||||
t.Run("NoAppHostname", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
protoApps := []*agentproto.WorkspaceApp{
|
||||
{
|
||||
Id: apps[0].ID[:],
|
||||
Url: apps[0].Url.String,
|
||||
External: apps[0].External,
|
||||
Slug: apps[0].Slug,
|
||||
DisplayName: apps[0].DisplayName,
|
||||
Command: apps[0].Command.String,
|
||||
Icon: apps[0].Icon,
|
||||
Subdomain: apps[0].Subdomain,
|
||||
SubdomainName: "", // Empty because AppHostname is empty
|
||||
SharingLevel: agentproto.WorkspaceApp_AUTHENTICATED,
|
||||
Healthcheck: &agentproto.WorkspaceApp_Healthcheck{
|
||||
Url: apps[0].HealthcheckUrl,
|
||||
Interval: durationpb.New(time.Duration(apps[0].HealthcheckInterval) * time.Second),
|
||||
Threshold: apps[0].HealthcheckThreshold,
|
||||
},
|
||||
Health: agentproto.WorkspaceApp_HEALTHY,
|
||||
Hidden: false,
|
||||
},
|
||||
{
|
||||
Id: apps[1].ID[:],
|
||||
Url: apps[1].Url.String,
|
||||
External: apps[1].External,
|
||||
Slug: apps[1].Slug,
|
||||
DisplayName: apps[1].DisplayName,
|
||||
Command: apps[1].Command.String,
|
||||
Icon: apps[1].Icon,
|
||||
Subdomain: false,
|
||||
SubdomainName: "",
|
||||
SharingLevel: agentproto.WorkspaceApp_PUBLIC,
|
||||
Healthcheck: &agentproto.WorkspaceApp_Healthcheck{
|
||||
Url: "",
|
||||
Interval: durationpb.New(0),
|
||||
Threshold: 0,
|
||||
},
|
||||
Health: agentproto.WorkspaceApp_DISABLED,
|
||||
Hidden: false,
|
||||
},
|
||||
{
|
||||
Id: apps[2].ID[:],
|
||||
Url: apps[2].Url.String,
|
||||
External: apps[2].External,
|
||||
Slug: apps[2].Slug,
|
||||
DisplayName: apps[2].DisplayName,
|
||||
Command: apps[2].Command.String,
|
||||
Icon: apps[2].Icon,
|
||||
Subdomain: false,
|
||||
SubdomainName: "",
|
||||
SharingLevel: agentproto.WorkspaceApp_OWNER,
|
||||
Healthcheck: &agentproto.WorkspaceApp_Healthcheck{
|
||||
Url: apps[2].HealthcheckUrl,
|
||||
Interval: durationpb.New(time.Duration(apps[2].HealthcheckInterval) * time.Second),
|
||||
Threshold: apps[2].HealthcheckThreshold,
|
||||
},
|
||||
Health: agentproto.WorkspaceApp_UNHEALTHY,
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
|
||||
mDB := dbmock.NewMockStore(gomock.NewController(t))
|
||||
|
||||
api := &agentapi.ManifestAPI{
|
||||
|
||||
Reference in New Issue
Block a user