feat: move shared ports out of experiment

This commit is contained in:
Garrett Delfosse 2024-05-01 20:20:40 +00:00
parent 845407fe7a
commit 3e44582162
10 changed files with 169 additions and 209 deletions

2
coderd/apidoc/docs.go generated
View File

@ -9517,7 +9517,6 @@ const docTemplate = `{
"type": "string",
"enum": [
"example",
"shared-ports",
"auto-fill-parameters"
],
"x-enum-comments": {
@ -9526,7 +9525,6 @@ const docTemplate = `{
},
"x-enum-varnames": [
"ExperimentExample",
"ExperimentSharedPorts",
"ExperimentAutoFillParameters"
]
},

View File

@ -8516,16 +8516,12 @@
},
"codersdk.Experiment": {
"type": "string",
"enum": ["example", "shared-ports", "auto-fill-parameters"],
"enum": ["example", "auto-fill-parameters"],
"x-enum-comments": {
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
"ExperimentExample": "This isn't used for anything."
},
"x-enum-varnames": [
"ExperimentExample",
"ExperimentSharedPorts",
"ExperimentAutoFillParameters"
]
"x-enum-varnames": ["ExperimentExample", "ExperimentAutoFillParameters"]
},
"codersdk.ExternalAuth": {
"type": "object",

View File

@ -1055,9 +1055,6 @@ func New(options *Options) *API {
r.Put("/autoupdates", api.putWorkspaceAutoupdates)
r.Get("/resolve-autostart", api.resolveAutostart)
r.Route("/port-share", func(r chi.Router) {
r.Use(
httpmw.RequireExperiment(api.Experiments, codersdk.ExperimentSharedPorts),
)
r.Get("/", api.workspaceAgentPortShares)
r.Post("/", api.postWorkspaceAgentPortShare)
r.Delete("/", api.deleteWorkspaceAgentPortShare)

View File

@ -19,11 +19,7 @@ func TestPostWorkspaceAgentPortShare(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
dep := coderdtest.DeploymentValues(t)
dep.Experiments = append(dep.Experiments, string(codersdk.ExperimentSharedPorts))
ownerClient, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: dep,
})
ownerClient, db := coderdtest.NewWithDatabase(t, nil)
owner := coderdtest.CreateFirstUser(t, ownerClient)
client, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
@ -140,11 +136,7 @@ func TestGetWorkspaceAgentPortShares(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
dep := coderdtest.DeploymentValues(t)
dep.Experiments = append(dep.Experiments, string(codersdk.ExperimentSharedPorts))
ownerClient, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: dep,
})
ownerClient, db := coderdtest.NewWithDatabase(t, nil)
owner := coderdtest.CreateFirstUser(t, ownerClient)
client, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
@ -180,11 +172,7 @@ func TestDeleteWorkspaceAgentPortShare(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
dep := coderdtest.DeploymentValues(t)
dep.Experiments = append(dep.Experiments, string(codersdk.ExperimentSharedPorts))
ownerClient, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: dep,
})
ownerClient, db := coderdtest.NewWithDatabase(t, nil)
owner := coderdtest.CreateFirstUser(t, ownerClient)
client, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)

View File

@ -257,7 +257,6 @@ func TestWorkspaceApps(t *testing.T) {
deploymentValues.DisablePathApps = serpent.Bool(opts.DisablePathApps)
deploymentValues.Dangerous.AllowPathAppSharing = serpent.Bool(opts.DangerousAllowPathAppSharing)
deploymentValues.Dangerous.AllowPathAppSiteOwnerAccess = serpent.Bool(opts.DangerousAllowPathAppSiteOwnerAccess)
deploymentValues.Experiments = append(deploymentValues.Experiments, string(codersdk.ExperimentSharedPorts))
if opts.DisableSubdomainApps {
opts.AppHost = ""

View File

@ -2192,8 +2192,7 @@ type Experiment string
const (
// Add new experiments here!
ExperimentExample Experiment = "example" // This isn't used for anything.
ExperimentSharedPorts Experiment = "shared-ports"
ExperimentExample Experiment = "example" // This isn't used for anything.
ExperimentAutoFillParameters Experiment = "auto-fill-parameters" // This should not be taken out of experiments until we have redesigned the feature.
)
@ -2201,9 +2200,7 @@ const (
// users to opt-in to via --experimental='*'.
// Experiments that are not ready for consumption by all users should
// not be included here and will be essentially hidden.
var ExperimentsAll = Experiments{
ExperimentSharedPorts,
}
var ExperimentsAll = Experiments{}
// Experiments is a list of experiments.
// Multiple experiments may be enabled at the same time.

1
docs/api/schemas.md generated
View File

@ -2666,7 +2666,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
| Value |
| ---------------------- |
| `example` |
| `shared-ports` |
| `auto-fill-parameters` |
## codersdk.ExternalAuth

View File

@ -17,12 +17,9 @@ import (
func TestWorkspacePortShare(t *testing.T) {
t.Parallel()
dep := coderdtest.DeploymentValues(t)
dep.Experiments = append(dep.Experiments, string(codersdk.ExperimentSharedPorts))
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
IncludeProvisionerDaemon: true,
DeploymentValues: dep,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{

View File

@ -1898,12 +1898,8 @@ export const Entitlements: Entitlement[] = [
];
// From codersdk/deployment.go
export type Experiment = "auto-fill-parameters" | "example" | "shared-ports";
export const Experiments: Experiment[] = [
"auto-fill-parameters",
"example",
"shared-ports",
];
export type Experiment = "auto-fill-parameters" | "example";
export const Experiments: Experiment[] = ["auto-fill-parameters", "example"];
// From codersdk/deployment.go
export type FeatureName =

View File

@ -65,7 +65,7 @@ export interface PortForwardButtonProps {
export const PortForwardButton: FC<PortForwardButtonProps> = (props) => {
const { agent } = props;
const { entitlements, experiments } = useDashboard();
const { entitlements } = useDashboard();
const paper = useClassName(classNames.paper, []);
const portsQuery = useQuery({
@ -103,7 +103,6 @@ export const PortForwardButton: FC<PortForwardButtonProps> = (props) => {
<PortForwardPopoverView
{...props}
listeningPorts={portsQuery.data?.ports}
portSharingExperimentEnabled={experiments.includes("shared-ports")}
portSharingControlsEnabled={
entitlements.features.control_shared_ports.enabled
}
@ -121,7 +120,6 @@ const getValidationSchema = (): Yup.AnyObjectSchema =>
interface PortForwardPopoverViewProps extends PortForwardButtonProps {
listeningPorts?: readonly WorkspaceAgentListeningPort[];
portSharingExperimentEnabled: boolean;
portSharingControlsEnabled: boolean;
}
@ -135,7 +133,6 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
template,
username,
listeningPorts,
portSharingExperimentEnabled,
portSharingControlsEnabled,
}) => {
const theme = useTheme();
@ -200,9 +197,9 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
filteredSharedPorts.every((sharedPort) => sharedPort.port !== port.port),
);
// only disable the form if shared port controls are entitled and the template doesn't allow sharing ports
const canSharePorts =
portSharingExperimentEnabled &&
!(portSharingControlsEnabled && template.max_port_share_level === "owner");
const canSharePorts = !(
portSharingControlsEnabled && template.max_port_share_level === "owner"
);
const canSharePortsPublic =
canSharePorts && template.max_port_share_level === "public";
@ -396,183 +393,179 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
})}
</Stack>
</div>
{portSharingExperimentEnabled && (
<div
css={{
padding: 20,
borderTop: `1px solid ${theme.palette.divider}`,
}}
>
<HelpTooltipTitle>Shared Ports</HelpTooltipTitle>
<HelpTooltipText css={{ color: theme.palette.text.secondary }}>
{canSharePorts
? "Ports can be shared with other Coder users or with the public. Changing the protocol may take up to 1 minute to take effect."
: "This workspace template does not allow sharing ports. Contact a template administrator to enable port sharing."}
</HelpTooltipText>
{canSharePorts && (
<div>
{filteredSharedPorts?.map((share) => {
const url = portForwardURL(
host,
share.port,
agent.name,
workspaceName,
username,
share.protocol,
);
const label = share.port;
return (
<Stack
key={share.port}
direction="row"
justifyContent="space-between"
alignItems="center"
<div
css={{
padding: 20,
borderTop: `1px solid ${theme.palette.divider}`,
}}
>
<HelpTooltipTitle>Shared Ports</HelpTooltipTitle>
<HelpTooltipText css={{ color: theme.palette.text.secondary }}>
{canSharePorts
? "Ports can be shared with other Coder users or with the public. Changing the protocol may take up to 1 minute to take effect."
: "This workspace template does not allow sharing ports. Contact a template administrator to enable port sharing."}
</HelpTooltipText>
{canSharePorts && (
<div>
{filteredSharedPorts?.map((share) => {
const url = portForwardURL(
host,
share.port,
agent.name,
workspaceName,
username,
share.protocol,
);
const label = share.port;
return (
<Stack
key={share.port}
direction="row"
justifyContent="space-between"
alignItems="center"
>
<Link
underline="none"
css={styles.portLink}
href={url}
target="_blank"
rel="noreferrer"
>
<Link
underline="none"
css={styles.portLink}
href={url}
target="_blank"
rel="noreferrer"
{share.share_level === "public" ? (
<LockOpenIcon css={{ width: 14, height: 14 }} />
) : (
<LockIcon css={{ width: 14, height: 14 }} />
)}
{label}
</Link>
<FormControl size="small" css={styles.protocolFormControl}>
<Select
css={styles.shareLevelSelect}
value={share.protocol}
onChange={async (event) => {
await upsertSharedPortMutation.mutateAsync({
agent_name: agent.name,
port: share.port,
protocol: event.target
.value as WorkspaceAgentPortShareProtocol,
share_level: share.share_level,
});
await sharedPortsQuery.refetch();
}}
>
<MenuItem value="http">HTTP</MenuItem>
<MenuItem value="https">HTTPS</MenuItem>
</Select>
</FormControl>
<Stack direction="row" justifyContent="flex-end">
<FormControl
size="small"
css={styles.shareLevelFormControl}
>
{share.share_level === "public" ? (
<LockOpenIcon css={{ width: 14, height: 14 }} />
) : (
<LockIcon css={{ width: 14, height: 14 }} />
)}
{label}
</Link>
<FormControl size="small" css={styles.protocolFormControl}>
<Select
css={styles.shareLevelSelect}
value={share.protocol}
value={share.share_level}
onChange={async (event) => {
await upsertSharedPortMutation.mutateAsync({
agent_name: agent.name,
port: share.port,
protocol: event.target
.value as WorkspaceAgentPortShareProtocol,
share_level: share.share_level,
protocol: share.protocol,
share_level: event.target
.value as WorkspaceAgentPortShareLevel,
});
await sharedPortsQuery.refetch();
}}
>
<MenuItem value="http">HTTP</MenuItem>
<MenuItem value="https">HTTPS</MenuItem>
<MenuItem value="authenticated">Authenticated</MenuItem>
{canSharePortsPublic ? (
<MenuItem value="public">Public</MenuItem>
) : (
disabledPublicMenuItem
)}
</Select>
</FormControl>
<Stack direction="row" justifyContent="flex-end">
<FormControl
size="small"
css={styles.shareLevelFormControl}
>
<Select
css={styles.shareLevelSelect}
value={share.share_level}
onChange={async (event) => {
await upsertSharedPortMutation.mutateAsync({
agent_name: agent.name,
port: share.port,
protocol: share.protocol,
share_level: event.target
.value as WorkspaceAgentPortShareLevel,
});
await sharedPortsQuery.refetch();
}}
>
<MenuItem value="authenticated">
Authenticated
</MenuItem>
{canSharePortsPublic ? (
<MenuItem value="public">Public</MenuItem>
) : (
disabledPublicMenuItem
)}
</Select>
</FormControl>
<Button
size="small"
variant="text"
css={styles.deleteButton}
onClick={async () => {
await deleteSharedPortMutation.mutateAsync({
agent_name: agent.name,
port: share.port,
});
await sharedPortsQuery.refetch();
<Button
size="small"
variant="text"
css={styles.deleteButton}
onClick={async () => {
await deleteSharedPortMutation.mutateAsync({
agent_name: agent.name,
port: share.port,
});
await sharedPortsQuery.refetch();
}}
>
<CloseIcon
css={{
width: 14,
height: 14,
color: theme.palette.text.primary,
}}
>
<CloseIcon
css={{
width: 14,
height: 14,
color: theme.palette.text.primary,
}}
/>
</Button>
</Stack>
/>
</Button>
</Stack>
);
})}
<form onSubmit={form.handleSubmit}>
<Stack
direction="column"
gap={2}
justifyContent="flex-end"
sx={{
marginTop: 2,
}}
>
<TextField
{...getFieldHelpers("port")}
disabled={isSubmitting}
label="Port"
size="small"
variant="outlined"
type="number"
value={form.values.port}
/>
<TextField
{...getFieldHelpers("protocol")}
disabled={isSubmitting}
fullWidth
select
value={form.values.protocol}
label="Protocol"
>
<MenuItem value="http">HTTP</MenuItem>
<MenuItem value="https">HTTPS</MenuItem>
</TextField>
<TextField
{...getFieldHelpers("share_level")}
disabled={isSubmitting}
fullWidth
select
value={form.values.share_level}
label="Sharing Level"
>
<MenuItem value="authenticated">Authenticated</MenuItem>
{canSharePortsPublic ? (
<MenuItem value="public">Public</MenuItem>
) : (
disabledPublicMenuItem
)}
</TextField>
<LoadingButton
variant="contained"
type="submit"
loading={isSubmitting}
disabled={!form.isValid}
>
Share Port
</LoadingButton>
</Stack>
</form>
</div>
)}
</div>
)}
);
})}
<form onSubmit={form.handleSubmit}>
<Stack
direction="column"
gap={2}
justifyContent="flex-end"
sx={{
marginTop: 2,
}}
>
<TextField
{...getFieldHelpers("port")}
disabled={isSubmitting}
label="Port"
size="small"
variant="outlined"
type="number"
value={form.values.port}
/>
<TextField
{...getFieldHelpers("protocol")}
disabled={isSubmitting}
fullWidth
select
value={form.values.protocol}
label="Protocol"
>
<MenuItem value="http">HTTP</MenuItem>
<MenuItem value="https">HTTPS</MenuItem>
</TextField>
<TextField
{...getFieldHelpers("share_level")}
disabled={isSubmitting}
fullWidth
select
value={form.values.share_level}
label="Sharing Level"
>
<MenuItem value="authenticated">Authenticated</MenuItem>
{canSharePortsPublic ? (
<MenuItem value="public">Public</MenuItem>
) : (
disabledPublicMenuItem
)}
</TextField>
<LoadingButton
variant="contained"
type="submit"
loading={isSubmitting}
disabled={!form.isValid}
>
Share Port
</LoadingButton>
</Stack>
</form>
</div>
)}
</div>
</>
);
};