Skip to content

Commit

Permalink
[public-api] Migrate envvarService (#19067)
Browse files Browse the repository at this point in the history
* Migrate envvarService

* update

* fix tests

* nit

* Fix

* Fix

* 💄

---------

Co-authored-by: Huiwen <[email protected]>
  • Loading branch information
jeanp413 and mustard-mh authored Nov 20, 2023
1 parent a409eb0 commit 4ac04ee
Show file tree
Hide file tree
Showing 19 changed files with 755 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@
*/

import { useMutation } from "@tanstack/react-query";
import { getGitpodService } from "../../service/service";
import { envVarClient } from "../../service/public-api";
import { EnvironmentVariableAdmission } from "@gitpod/public-api/lib/gitpod/v1/envvar_pb";

type SetProjectEnvVarArgs = {
projectId: string;
name: string;
value: string;
censored: boolean;
admission: EnvironmentVariableAdmission;
};
export const useSetProjectEnvVar = () => {
return useMutation<void, Error, SetProjectEnvVarArgs>(async ({ projectId, name, value, censored }) => {
return getGitpodService().server.setProjectEnvironmentVariable(projectId, name, value, censored);
return useMutation<void, Error, SetProjectEnvVarArgs>(async ({ projectId, name, value, admission }) => {
await envVarClient.createConfigurationEnvironmentVariable({
name,
value,
configurationId: projectId,
admission,
});
});
};
4 changes: 3 additions & 1 deletion components/dashboard/src/data/setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ import * as WorkspaceClasses from "@gitpod/public-api/lib/gitpod/v1/workspace_pb
import * as PaginationClasses from "@gitpod/public-api/lib/gitpod/v1/pagination_pb";
import * as ConfigurationClasses from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
import * as AuthProviderClasses from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";
import * as EnvVarClasses from "@gitpod/public-api/lib/gitpod/v1/envvar_pb";

// This is used to version the cache
// If data we cache changes in a non-backwards compatible way, increment this version
// That will bust any previous cache versions a client may have stored
const CACHE_VERSION = "5";
const CACHE_VERSION = "6";

export function noPersistence(queryKey: QueryKey): QueryKey {
return [...queryKey, "no-persistence"];
Expand Down Expand Up @@ -146,6 +147,7 @@ function initializeMessages() {
...Object.values(PaginationClasses),
...Object.values(ConfigurationClasses),
...Object.values(AuthProviderClasses),
...Object.values(EnvVarClasses),
];
for (const c of constr) {
if ((c as any).prototype instanceof Message) {
Expand Down
42 changes: 29 additions & 13 deletions components/dashboard/src/projects/ProjectVariables.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See License.AGPL.txt in the project root for license information.
*/

import { Project, ProjectEnvVar } from "@gitpod/gitpod-protocol";
import { Project } from "@gitpod/gitpod-protocol";
import { useCallback, useEffect, useState } from "react";
import { Redirect } from "react-router";
import Alert from "../components/Alert";
Expand All @@ -13,23 +13,29 @@ import InfoBox from "../components/InfoBox";
import { Item, ItemField, ItemFieldContextMenu, ItemsList } from "../components/ItemsList";
import Modal, { ModalBody, ModalFooter, ModalFooterAlert, ModalHeader } from "../components/Modal";
import { Heading2, Subheading } from "../components/typography/headings";
import { getGitpodService } from "../service/service";
import { useCurrentProject } from "./project-context";
import { ProjectSettingsPage } from "./ProjectSettings";
import { Button } from "../components/Button";
import { useSetProjectEnvVar } from "../data/projects/set-project-env-var-mutation";
import { envVarClient } from "../service/public-api";
import {
ConfigurationEnvironmentVariable,
EnvironmentVariableAdmission,
} from "@gitpod/public-api/lib/gitpod/v1/envvar_pb";

export default function ProjectVariablesPage() {
const { project, loading } = useCurrentProject();
const [envVars, setEnvVars] = useState<ProjectEnvVar[]>([]);
const [envVars, setEnvVars] = useState<ConfigurationEnvironmentVariable[]>([]);
const [showAddVariableModal, setShowAddVariableModal] = useState<boolean>(false);

const updateEnvVars = async () => {
if (!project) {
return;
}
const vars = await getGitpodService().server.getProjectEnvironmentVariables(project.id);
const sortedVars = vars.sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1));
const resp = await envVarClient.listConfigurationEnvironmentVariables({ configurationId: project.id });
const sortedVars = resp.environmentVariables.sort((a, b) =>
a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1,
);
setEnvVars(sortedVars);
};

Expand All @@ -39,7 +45,7 @@ export default function ProjectVariablesPage() {
}, [project]);

const deleteEnvVar = async (variableId: string) => {
await getGitpodService().server.deleteProjectEnvironmentVariable(variableId);
await envVarClient.deleteConfigurationEnvironmentVariable({ envVarId: variableId });
updateEnvVars();
};

Expand Down Expand Up @@ -85,7 +91,11 @@ export default function ProjectVariablesPage() {
return (
<Item key={variable.id} className="grid grid-cols-3 items-center">
<ItemField className="truncate">{variable.name}</ItemField>
<ItemField>{variable.censored ? "Hidden" : "Visible"}</ItemField>
<ItemField>
{variable.admission === EnvironmentVariableAdmission.PREBUILD
? "Hidden"
: "Visible"}
</ItemField>
<ItemField className="flex justify-end">
<ItemFieldContextMenu
menuEntries={[
Expand All @@ -110,7 +120,7 @@ export default function ProjectVariablesPage() {
function AddVariableModal(props: { project?: Project; onClose: () => void }) {
const [name, setName] = useState<string>("");
const [value, setValue] = useState<string>("");
const [censored, setCensored] = useState<boolean>(true);
const [admission, setAdmission] = useState<EnvironmentVariableAdmission>(EnvironmentVariableAdmission.PREBUILD);
const setProjectEnvVar = useSetProjectEnvVar();

const addVariable = useCallback(async () => {
Expand All @@ -123,11 +133,11 @@ function AddVariableModal(props: { project?: Project; onClose: () => void }) {
projectId: props.project.id,
name,
value,
censored,
admission,
},
{ onSuccess: props.onClose },
);
}, [censored, name, props.onClose, props.project, setProjectEnvVar, value]);
}, [admission, name, props.onClose, props.project, setProjectEnvVar, value]);

return (
<Modal visible onClose={props.onClose} onSubmit={addVariable}>
Expand Down Expand Up @@ -164,10 +174,16 @@ function AddVariableModal(props: { project?: Project; onClose: () => void }) {
<CheckboxInputField
label="Hide Variable in Workspaces"
hint="Unset this environment variable so that it's not accessible from the terminal in workspaces."
checked={censored}
onChange={() => setCensored(!censored)}
checked={admission === EnvironmentVariableAdmission.PREBUILD}
onChange={() =>
setAdmission(
admission === EnvironmentVariableAdmission.PREBUILD
? EnvironmentVariableAdmission.EVERYWHERE
: EnvironmentVariableAdmission.PREBUILD,
)
}
/>
{!censored && (
{admission === EnvironmentVariableAdmission.EVERYWHERE && (
<div className="mt-4">
<InfoBox>
This variable will be visible to anyone who starts a Gitpod workspace for your repository.
Expand Down
231 changes: 231 additions & 0 deletions components/dashboard/src/service/json-rpc-envvar-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/**
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License.AGPL.txt in the project root for license information.
*/

import { PromiseClient } from "@connectrpc/connect";
import { PartialMessage } from "@bufbuild/protobuf";
import { EnvironmentVariableService } from "@gitpod/public-api/lib/gitpod/v1/envvar_connect";
import {
CreateConfigurationEnvironmentVariableRequest,
CreateConfigurationEnvironmentVariableResponse,
CreateUserEnvironmentVariableRequest,
CreateUserEnvironmentVariableResponse,
DeleteConfigurationEnvironmentVariableRequest,
DeleteConfigurationEnvironmentVariableResponse,
DeleteUserEnvironmentVariableRequest,
DeleteUserEnvironmentVariableResponse,
EnvironmentVariableAdmission,
ListConfigurationEnvironmentVariablesRequest,
ListConfigurationEnvironmentVariablesResponse,
ListUserEnvironmentVariablesRequest,
ListUserEnvironmentVariablesResponse,
ResolveWorkspaceEnvironmentVariablesRequest,
ResolveWorkspaceEnvironmentVariablesResponse,
UpdateConfigurationEnvironmentVariableRequest,
UpdateConfigurationEnvironmentVariableResponse,
UpdateUserEnvironmentVariableRequest,
UpdateUserEnvironmentVariableResponse,
} from "@gitpod/public-api/lib/gitpod/v1/envvar_pb";
import { converter } from "./public-api";
import { getGitpodService } from "./service";
import { UserEnvVar, UserEnvVarValue } from "@gitpod/gitpod-protocol";
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";

export class JsonRpcEnvvarClient implements PromiseClient<typeof EnvironmentVariableService> {
async listUserEnvironmentVariables(
req: PartialMessage<ListUserEnvironmentVariablesRequest>,
): Promise<ListUserEnvironmentVariablesResponse> {
const result = new ListUserEnvironmentVariablesResponse();
const userEnvVars = await getGitpodService().server.getAllEnvVars();
result.environmentVariables = userEnvVars.map((i) => converter.toUserEnvironmentVariable(i));

return result;
}

async updateUserEnvironmentVariable(
req: PartialMessage<UpdateUserEnvironmentVariableRequest>,
): Promise<UpdateUserEnvironmentVariableResponse> {
if (!req.envVarId) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "envVarId is required");
}

const response = new UpdateUserEnvironmentVariableResponse();

const userEnvVars = await getGitpodService().server.getAllEnvVars();
const userEnvVarfound = userEnvVars.find((i) => i.id === req.envVarId);
if (userEnvVarfound) {
const variable: UserEnvVarValue = {
id: req.envVarId,
name: req.name ?? userEnvVarfound.name,
value: req.value ?? userEnvVarfound.value,
repositoryPattern: req.repositoryPattern ?? userEnvVarfound.repositoryPattern,
};
variable.repositoryPattern = UserEnvVar.normalizeRepoPattern(variable.repositoryPattern);

await getGitpodService().server.setEnvVar(variable);

const updatedUserEnvVars = await getGitpodService().server.getAllEnvVars();
const updatedUserEnvVar = updatedUserEnvVars.find((i) => i.id === req.envVarId);
if (!updatedUserEnvVar) {
throw new ApplicationError(ErrorCodes.INTERNAL_SERVER_ERROR, "could not update env variable");
}

response.environmentVariable = converter.toUserEnvironmentVariable(updatedUserEnvVar);
return response;
}

throw new ApplicationError(ErrorCodes.NOT_FOUND, "env variable not found");
}

async createUserEnvironmentVariable(
req: PartialMessage<CreateUserEnvironmentVariableRequest>,
): Promise<CreateUserEnvironmentVariableResponse> {
if (!req.name || !req.value || !req.repositoryPattern) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "name, value and repositoryPattern are required");
}

const response = new CreateUserEnvironmentVariableResponse();

const variable: UserEnvVarValue = {
name: req.name,
value: req.value,
repositoryPattern: req.repositoryPattern,
};
variable.repositoryPattern = UserEnvVar.normalizeRepoPattern(variable.repositoryPattern);

await getGitpodService().server.setEnvVar(variable);

const updatedUserEnvVars = await getGitpodService().server.getAllEnvVars();
const updatedUserEnvVar = updatedUserEnvVars.find(
(v) => v.name === variable.name && v.repositoryPattern === variable.repositoryPattern,
);
if (!updatedUserEnvVar) {
throw new ApplicationError(ErrorCodes.INTERNAL_SERVER_ERROR, "could not update env variable");
}

response.environmentVariable = converter.toUserEnvironmentVariable(updatedUserEnvVar);

return response;
}

async deleteUserEnvironmentVariable(
req: PartialMessage<DeleteUserEnvironmentVariableRequest>,
): Promise<DeleteUserEnvironmentVariableResponse> {
if (!req.envVarId) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "envVarId is required");
}

const variable: UserEnvVarValue = {
id: req.envVarId,
name: "",
value: "",
repositoryPattern: "",
};

await getGitpodService().server.deleteEnvVar(variable);

const response = new DeleteUserEnvironmentVariableResponse();
return response;
}

async listConfigurationEnvironmentVariables(
req: PartialMessage<ListConfigurationEnvironmentVariablesRequest>,
): Promise<ListConfigurationEnvironmentVariablesResponse> {
if (!req.configurationId) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "configurationId is required");
}

const result = new ListConfigurationEnvironmentVariablesResponse();
const projectEnvVars = await getGitpodService().server.getProjectEnvironmentVariables(req.configurationId);
result.environmentVariables = projectEnvVars.map((i) => converter.toConfigurationEnvironmentVariable(i));

return result;
}

async updateConfigurationEnvironmentVariable(
req: PartialMessage<UpdateConfigurationEnvironmentVariableRequest>,
): Promise<UpdateConfigurationEnvironmentVariableResponse> {
if (!req.envVarId) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "envVarId is required");
}
if (!req.configurationId) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "configurationId is required");
}

const response = new UpdateConfigurationEnvironmentVariableResponse();

const projectEnvVars = await getGitpodService().server.getProjectEnvironmentVariables(req.configurationId);
const projectEnvVarfound = projectEnvVars.find((i) => i.id === req.envVarId);
if (projectEnvVarfound) {
await getGitpodService().server.setProjectEnvironmentVariable(
req.configurationId,
req.name ?? projectEnvVarfound.name,
req.value ?? "",
(req.admission === EnvironmentVariableAdmission.PREBUILD ? true : false) ?? projectEnvVarfound.censored,
);

const updatedProjectEnvVars = await getGitpodService().server.getProjectEnvironmentVariables(
req.configurationId,
);
const updatedProjectEnvVar = updatedProjectEnvVars.find((i) => i.id === req.envVarId);
if (!updatedProjectEnvVar) {
throw new ApplicationError(ErrorCodes.INTERNAL_SERVER_ERROR, "could not update env variable");
}

response.environmentVariable = converter.toConfigurationEnvironmentVariable(updatedProjectEnvVar);
return response;
}

throw new ApplicationError(ErrorCodes.NOT_FOUND, "env variable not found");
}

async createConfigurationEnvironmentVariable(
req: PartialMessage<CreateConfigurationEnvironmentVariableRequest>,
): Promise<CreateConfigurationEnvironmentVariableResponse> {
if (!req.configurationId || !req.name || !req.value) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "configurationId, name and value are required");
}

const response = new CreateConfigurationEnvironmentVariableResponse();

await getGitpodService().server.setProjectEnvironmentVariable(
req.configurationId,
req.name,
req.value,
req.admission === EnvironmentVariableAdmission.PREBUILD ? true : false,
);

const updatedProjectEnvVars = await getGitpodService().server.getProjectEnvironmentVariables(
req.configurationId,
);
const updatedProjectEnvVar = updatedProjectEnvVars.find((v) => v.name === req.name);
if (!updatedProjectEnvVar) {
throw new ApplicationError(ErrorCodes.INTERNAL_SERVER_ERROR, "could not create env variable");
}

response.environmentVariable = converter.toConfigurationEnvironmentVariable(updatedProjectEnvVar);

return response;
}

async deleteConfigurationEnvironmentVariable(
req: PartialMessage<DeleteConfigurationEnvironmentVariableRequest>,
): Promise<DeleteConfigurationEnvironmentVariableResponse> {
if (!req.envVarId) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "envVarId is required");
}

await getGitpodService().server.deleteProjectEnvironmentVariable(req.envVarId);

const response = new DeleteConfigurationEnvironmentVariableResponse();
return response;
}

async resolveWorkspaceEnvironmentVariables(
req: PartialMessage<ResolveWorkspaceEnvironmentVariablesRequest>,
): Promise<ResolveWorkspaceEnvironmentVariablesResponse> {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "Unimplemented");
}
}
Loading

0 comments on commit 4ac04ee

Please sign in to comment.