Skip to content
This repository has been archived by the owner on Jul 2, 2024. It is now read-only.

EVG-19946: Support Docker on provider settings page #2017

Merged
merged 12 commits into from
Sep 18, 2023
100 changes: 75 additions & 25 deletions cypress/integration/distroSettings/provider_section.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,88 @@
import { save } from "./utils";

describe("provider section", () => {
beforeEach(() => {
cy.visit("/distro/localhost/settings/provider");
});
describe("static", () => {
beforeEach(() => {
cy.visit("/distro/localhost/settings/provider");
});

it("successfully updates static provider fields", () => {
cy.dataCy("provider-select").contains("Static IP/VM");

// Correct fields are displayed
cy.dataCy("provider-settings").within(() => {
cy.get("button").should("have.length", 1);
cy.get("textarea").should("have.length", 1);
cy.get("input[type=checkbox]").should("have.length", 1);
cy.get("input[type=text]").should("have.length", 0);
});

cy.getInputByLabel("User Data").type("my user data");
cy.getInputByLabel("Merge with existing user data").check({
force: true,
});
cy.contains("button", "Add security group").click();
cy.getInputByLabel("Security Group ID").type("group-1234");

save();
cy.validateToast("success");

it("successfully updates static provider fields", () => {
cy.dataCy("provider-select").contains("Static IP/VM");
cy.getInputByLabel("User Data").clear();
cy.getInputByLabel("Merge with existing user data").uncheck({
force: true,
});
cy.dataCy("delete-item-button").click();

// Correct fields are displayed
cy.dataCy("provider-settings").within(() => {
cy.get("button").should("have.length", 1);
cy.get("textarea").should("have.length", 1);
cy.get("input[type=checkbox]").should("have.length", 1);
cy.get("input[type=text]").should("have.length", 0);
save();
cy.validateToast("success");
});
});

cy.getInputByLabel("User Data").type("my user data");
cy.getInputByLabel("Merge with existing user data").check({
force: true,
describe.only("docker", () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
describe.only("docker", () => {
describe("docker", () => {

beforeEach(() => {
cy.visit("/distro/ubuntu1604-container-test/settings/provider");
});
cy.contains("button", "Add security group").click();
cy.getInputByLabel("Security Group ID").type("group-1234");

save();
cy.validateToast("success");
it("successfully updates docker provider fields", () => {
cy.dataCy("provider-select").contains("Docker");

cy.getInputByLabel("User Data").clear();
cy.getInputByLabel("Merge with existing user data").uncheck({
force: true,
});
cy.dataCy("delete-item-button").click();
// Correct fields are displayed
cy.dataCy("provider-settings").within(() => {
cy.getInputByLabel("Docker Image URL").should("exist");
cy.getInputByLabel("Image Build Method").should("exist");
cy.getInputByLabel("Username for Registries").should("exist");
cy.getInputByLabel("Password for Registries").should("exist");
cy.getInputByLabel("Container Pool ID").should("exist");
cy.getInputByLabel("Pool Mapping Information").should("exist");
cy.getInputByLabel("User Data").should("exist");
cy.getInputByLabel("Merge with existing user data").should("exist");
cy.contains("button", "Add security group").should("exist");
});

// Change field values.
cy.selectLGOption("Image Build Method", "Pull");
cy.selectLGOption("Container Pool ID", /^test-pool$/);
cy.getInputByLabel("Username for Registries").type("username");
cy.getInputByLabel("Password for Registries").type("password");
cy.getInputByLabel("User Data").type("my user data");
cy.getInputByLabel("Merge with existing user data").check({
force: true,
});
save();
cy.validateToast("success");

save();
cy.validateToast("success");
// Revert fields to original values.
cy.selectLGOption("Image Build Method", "Import");
cy.selectLGOption("Container Pool ID", "ubuntu-test-pool");
cy.getInputByLabel("Username for Registries").clear();
cy.getInputByLabel("Password for Registries").clear();
cy.getInputByLabel("User Data").clear();
cy.getInputByLabel("Merge with existing user data").uncheck({
force: true,
});

save();
cy.validateToast("success");
});
});
});
2 changes: 2 additions & 0 deletions src/components/SpruceForm/Widgets/LeafyGreenWidgets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ export const LeafyGreenTextArea: React.FC<SpruceWidgetProps> = ({
label,
onChange,
options,
placeholder,
rawErrors,
readonly,
value,
Expand Down Expand Up @@ -368,6 +369,7 @@ export const LeafyGreenTextArea: React.FC<SpruceWidgetProps> = ({
<ElementWrapper css={elementWrapperCSS}>
<TextArea
ref={el}
placeholder={placeholder || undefined}
data-cy={dataCy}
label={label}
disabled={disabled || readonly}
Expand Down
94 changes: 59 additions & 35 deletions src/gql/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,19 @@ export enum CommunicationMethod {
Ssh = "SSH",
}

export type ContainerPool = {
__typename?: "ContainerPool";
distro: Scalars["String"]["output"];
id: Scalars["String"]["output"];
maxContainers: Scalars["Int"]["output"];
port: Scalars["Int"]["output"];
};

export type ContainerPoolsConfig = {
__typename?: "ContainerPoolsConfig";
pools: Array<ContainerPool>;
};

export type ContainerResources = {
__typename?: "ContainerResources";
cpu: Scalars["Int"]["output"];
Expand Down Expand Up @@ -2324,6 +2337,7 @@ export type SpruceConfig = {
__typename?: "SpruceConfig";
banner?: Maybe<Scalars["String"]["output"]>;
bannerTheme?: Maybe<Scalars["String"]["output"]>;
containerPools?: Maybe<ContainerPoolsConfig>;
githubOrgs: Array<Scalars["String"]["output"]>;
jira?: Maybe<JiraConfig>;
providers?: Maybe<CloudProviderConfig>;
Expand Down Expand Up @@ -7780,41 +7794,6 @@ export type RepoSettingsQuery = {
};
};

export type SpruceConfigQueryVariables = Exact<{ [key: string]: never }>;

export type SpruceConfigQuery = {
__typename?: "Query";
spruceConfig?: {
__typename?: "SpruceConfig";
banner?: string | null;
bannerTheme?: string | null;
jira?: { __typename?: "JiraConfig"; host?: string | null } | null;
providers?: {
__typename?: "CloudProviderConfig";
aws?: {
__typename?: "AWSConfig";
maxVolumeSizePerUser?: number | null;
pod?: {
__typename?: "AWSPodConfig";
ecs?: {
__typename?: "ECSConfig";
maxCPU: number;
maxMemoryMb: number;
} | null;
} | null;
} | null;
} | null;
slack?: { __typename?: "SlackConfig"; name?: string | null } | null;
spawnHost: {
__typename?: "SpawnHostConfig";
spawnHostsPerUser: number;
unexpirableHostsPerUser: number;
unexpirableVolumesPerUser: number;
};
ui?: { __typename?: "UIConfig"; defaultProject: string } | null;
} | null;
};

export type SystemLogsQueryVariables = Exact<{
id: Scalars["String"]["input"];
execution?: InputMaybe<Scalars["Int"]["input"]>;
Expand Down Expand Up @@ -8641,6 +8620,51 @@ export type SpawnTaskQuery = {
} | null;
};

export type SpruceConfigQueryVariables = Exact<{ [key: string]: never }>;

export type SpruceConfigQuery = {
__typename?: "Query";
spruceConfig?: {
__typename?: "SpruceConfig";
banner?: string | null;
bannerTheme?: string | null;
containerPools?: {
__typename?: "ContainerPoolsConfig";
pools: Array<{
__typename?: "ContainerPool";
distro: string;
id: string;
maxContainers: number;
port: number;
}>;
} | null;
jira?: { __typename?: "JiraConfig"; host?: string | null } | null;
providers?: {
__typename?: "CloudProviderConfig";
aws?: {
__typename?: "AWSConfig";
maxVolumeSizePerUser?: number | null;
pod?: {
__typename?: "AWSPodConfig";
ecs?: {
__typename?: "ECSConfig";
maxCPU: number;
maxMemoryMb: number;
} | null;
} | null;
} | null;
} | null;
slack?: { __typename?: "SlackConfig"; name?: string | null } | null;
spawnHost: {
__typename?: "SpawnHostConfig";
spawnHostsPerUser: number;
unexpirableHostsPerUser: number;
unexpirableVolumesPerUser: number;
};
ui?: { __typename?: "UIConfig"; defaultProject: string } | null;
} | null;
};

export type SubnetAvailabilityZonesQueryVariables = Exact<{
[key: string]: never;
}>;
Expand Down
2 changes: 1 addition & 1 deletion src/gql/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import GET_PROJECTS from "./get-projects.graphql";
import GET_MY_PUBLIC_KEYS from "./get-public-keys.graphql";
import GET_REPO_EVENT_LOGS from "./get-repo-event-logs.graphql";
import GET_REPO_SETTINGS from "./get-repo-settings.graphql";
import GET_SPRUCE_CONFIG from "./get-spruce-config.graphql";
import GET_SYSTEM_LOGS from "./get-system-logs.graphql";
import GET_TASK_ALL_EXECUTIONS from "./get-task-all-executions.graphql";
import GET_TASK_EVENT_LOGS from "./get-task-event-logs.graphql";
Expand All @@ -71,6 +70,7 @@ import PROJECT_HEALTH_VIEW from "./project-health-view.graphql";
import GET_PROJECT_PATCHES from "./project-patches.graphql";
import GET_SPAWN_EXPIRATION_INFO from "./spawn-expiration.graphql";
import GET_SPAWN_TASK from "./spawn-task.graphql";
import GET_SPRUCE_CONFIG from "./spruce-config.graphql";
Copy link
Contributor

Choose a reason for hiding this comment

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

We've been removing GET_ from the import/export name too

Suggested change
import GET_SPRUCE_CONFIG from "./spruce-config.graphql";
import SPRUCE_CONFIG from "./spruce-config.graphql";

import GET_SUBNET_AVAILABILITY_ZONES from "./subnet-availability-zones.graphql";
import TASK_QUEUE_DISTROS from "./task-queue-distros.graphql";
import USER_DISTRO_SETTINGS_PERMISSIONS from "./user-distro-settings-permissions.graphql";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ query SpruceConfig {
spruceConfig {
banner
bannerTheme
containerPools {
pools {
distro
id
maxContainers
port
}
}
jira {
host
}
Expand Down
21 changes: 20 additions & 1 deletion src/pages/distroSettings/tabs/ProviderTab/ProviderTab.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import { useMemo } from "react";
import { DistroSettingsTabRoutes } from "constants/routes";
import { useSpruceConfig } from "hooks";
import { useDistroSettingsContext } from "pages/distroSettings/Context";
import { omitTypename } from "utils/string";
import { BaseTab } from "../BaseTab";
import { getFormSchema } from "./getFormSchema";
import { TabProps } from "./types";

export const ProviderTab: React.FC<TabProps> = ({ distroData }) => {
const initialFormState = distroData;
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] No need to rename this var (even though I think we have been doing it unnecessarily in every tab 😂)


const formSchema = useMemo(() => getFormSchema(), []);
const { getTab } = useDistroSettingsContext();
const { formData } = getTab(DistroSettingsTabRoutes.Provider);

const { containerPools } = useSpruceConfig();
const { pools = [] } = containerPools || {};

const selectedPoolId = formData?.providerSettings?.containerPoolId;
const selectedPool = pools.find((p) => p.id === selectedPoolId) ?? null;
const poolMappingInfo = selectedPool
? JSON.stringify(omitTypename(selectedPool), null, 4)
: "";

const formSchema = useMemo(
() => getFormSchema({ pools, poolMappingInfo }),
[pools, poolMappingInfo]
);
Copy link
Contributor

Choose a reason for hiding this comment

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

The useMemo will run on every render when pools is undefined. You can avoid doing that by casting pools to an array closer to where it's used. Another place to cast pools is in getFormSchema which is good if the function were reused so we only have to type cast in one place.

Suggested change
const { pools = [] } = containerPools || {};
const selectedPoolId = formData?.providerSettings?.containerPoolId;
const selectedPool = pools.find((p) => p.id === selectedPoolId) ?? null;
const poolMappingInfo = selectedPool
? JSON.stringify(omitTypename(selectedPool), null, 4)
: "";
const formSchema = useMemo(
() => getFormSchema({ pools, poolMappingInfo }),
[pools, poolMappingInfo]
);
const { pools } = containerPools || {};
const selectedPoolId = formData?.providerSettings?.containerPoolId;
const selectedPool = pools?.find((p) => p.id === selectedPoolId) ?? null;
const poolMappingInfo = selectedPool
? JSON.stringify(omitTypename(selectedPool), null, 4)
: "";
const formSchema = useMemo(
() => getFormSchema({ pools: pools || [], poolMappingInfo }),
[pools, poolMappingInfo]
);


return (
<BaseTab formSchema={formSchema} initialFormState={initialFormState} />
Expand Down
Loading