diff --git a/components/dashboard/src/teams/TeamSettings.tsx b/components/dashboard/src/teams/TeamSettings.tsx index 322542ac9c4590..5510a462b93a6a 100644 --- a/components/dashboard/src/teams/TeamSettings.tsx +++ b/components/dashboard/src/teams/TeamSettings.tsx @@ -18,7 +18,7 @@ import { OrganizationInfo, useCurrentOrg, useOrganizationsInvalidator } from ".. import { useUpdateOrgMutation } from "../data/organizations/update-org-mutation"; import { useOnBlurError } from "../hooks/use-onblur-error"; import { teamsService } from "../service/public-api"; -import { getGitpodService, gitpodHostUrl } from "../service/service"; +import { gitpodHostUrl } from "../service/service"; import { useCurrentUser } from "../user-context"; import { OrgSettingsPage } from "./OrgSettingsPage"; import { useToast } from "../components/toasts/Toasts"; @@ -170,24 +170,9 @@ function OrgSettingsForm(props: { org?: OrganizationInfo }) { const { org } = props; const { data: settings, isLoading } = useOrgSettingsQuery(); const updateTeamSettings = useUpdateOrgSettingsMutation(); - const server = getGitpodService().server; const [defaultWorkspaceImage, setDefaultWorkspaceImage] = useState(settings?.defaultWorkspaceImage ?? ""); const { toast } = useToast(); - const w = window as any; - const testRun = async () => { - console.log("==========", settings?.defaultWorkspaceImage); - if (org?.id) { - const data = await server - .verifyImageAccessibility(settings?.defaultWorkspaceImage ?? "") - .catch(console.error); - console.log(data); - } - }; - if (!w.testRun) { - w.testRun = testRun; - } - useEffect(() => { if (!settings) { return; diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts index e6ab48e00a2afc..2d218b14f45b7b 100644 --- a/components/gitpod-protocol/src/gitpod-service.ts +++ b/components/gitpod-protocol/src/gitpod-service.ts @@ -31,7 +31,6 @@ import { WorkspaceContext, LinkedInProfile, SuggestedRepository, - ImageAccessibility, } from "./protocol"; import { Team, @@ -174,8 +173,6 @@ export interface GitpodServer extends JsonRpcServer, AdminServer, getOrgAuthProviders(params: GitpodServer.GetOrgAuthProviderParams): Promise; deleteOrgAuthProvider(params: GitpodServer.DeleteOrgAuthProviderParams): Promise; - verifyImageAccessibility(image: string): Promise; - // Dedicated, Dedicated, Dedicated getOnboardingState(): Promise; diff --git a/components/gitpod-protocol/src/messaging/error.ts b/components/gitpod-protocol/src/messaging/error.ts index 3d621c5480eae7..685b56c4671f18 100644 --- a/components/gitpod-protocol/src/messaging/error.ts +++ b/components/gitpod-protocol/src/messaging/error.ts @@ -5,6 +5,7 @@ */ import { scrubber } from "../util/scrubbing"; +import * as grpc from "@grpc/grpc-js"; export class ApplicationError extends Error { constructor(public readonly code: ErrorCode, message: string, public readonly data?: any) { @@ -36,6 +37,31 @@ export namespace ApplicationError { throw e; } } + + export function fromGRPCError(e: grpc.ServerErrorResponse, data?: any): ApplicationError { + return new ApplicationError(categorizeRPCError(e), e.message, data); + } + + export function categorizeRPCError(e: grpc.ServerErrorResponse): ErrorCode { + // Mostly align to https://github.com/gitpod-io/gitpod/blob/ef95e6f3ca0bf314c40da1b83251423c2208d175/components/public-api-server/pkg/proxy/errors.go#L25 + switch (e.code) { + case grpc.status.INVALID_ARGUMENT: + return ErrorCodes.BAD_REQUEST; + case grpc.status.UNAUTHENTICATED: + return ErrorCodes.NOT_AUTHENTICATED; + case grpc.status.PERMISSION_DENIED: + return ErrorCodes.PERMISSION_DENIED; // or UserBlocked + case grpc.status.NOT_FOUND: + return ErrorCodes.NOT_FOUND; + case grpc.status.ALREADY_EXISTS: + return ErrorCodes.CONFLICT; + case grpc.status.FAILED_PRECONDITION: + return ErrorCodes.PRECONDITION_FAILED; + case grpc.status.RESOURCE_EXHAUSTED: + return ErrorCodes.TOO_MANY_REQUESTS; + } + return ErrorCodes.INTERNAL_SERVER_ERROR; + } } export namespace ErrorCode { diff --git a/components/gitpod-protocol/src/protocol.ts b/components/gitpod-protocol/src/protocol.ts index 5d6c129925f1bf..d9642e5a81ba90 100644 --- a/components/gitpod-protocol/src/protocol.ts +++ b/components/gitpod-protocol/src/protocol.ts @@ -337,12 +337,6 @@ export type IDESettings = { defaultDesktopIde?: string; }; -export enum ImageAccessibility { - Accessible = "accessible", - NotFound = "not-found", - Unauthorized = "unauthorized", -} - export interface WorkspaceClasses { regular?: string; prebuild?: string; diff --git a/components/server/src/auth/rate-limiter.ts b/components/server/src/auth/rate-limiter.ts index 6a98fa74c6bdc6..6c1f4d1abc1780 100644 --- a/components/server/src/auth/rate-limiter.ts +++ b/components/server/src/auth/rate-limiter.ts @@ -115,7 +115,6 @@ const defaultFunctions: FunctionsConfig = { createProject: { group: "default", points: 1 }, getTeamProjects: { group: "default", points: 1 }, deleteProject: { group: "default", points: 1 }, - verifyImageAccessibility: { group: "default", points: 1 }, findPrebuilds: { group: "default", points: 1 }, getPrebuild: { group: "default", points: 1 }, findPrebuildByWorkspaceID: { group: "default", points: 1 }, diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index 84b1d20d59725b..3f38ae965ba1fa 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -127,7 +127,6 @@ import { ClientMetadata, traceClientMetadata } from "../websocket/websocket-conn import { EmailDomainFilterEntry, EnvVarWithValue, - ImageAccessibility, LinkedInProfile, OpenPrebuildContext, ProjectEnvVar, @@ -2509,7 +2508,15 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { const user = await this.checkAndBlockUser("updateOrgSettings"); traceAPIParams(ctx, { orgId, userId: user.id }); await this.guardTeamOperation(orgId, "update"); - // TODO: call ImageBuilder ResolveBaseImage to dry test if we can access this image + if (settings.defaultWorkspaceImage?.trim()) { + try { + await this.workspaceService.resolveBaseImage(ctx, user, settings.defaultWorkspaceImage); + } catch (e) { + // we could map proper response message according to e.code + // see https://github.com/gitpod-io/gitpod/blob/ef95e6f3ca0bf314c40da1b83251423c2208d175/components/image-builder-mk3/pkg/orchestrator/orchestrator_test.go#L178 + throw ApplicationError.fromGRPCError(e); + } + } return this.organizationService.updateSettings(user.id, orgId, settings); } @@ -3322,19 +3329,6 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { } } - async verifyImageAccessibility(ctx: TraceContext, image: string): Promise { - traceAPIParams(ctx, { image }); - const user = await this.checkAndBlockUser("verifyImageAccessibility"); - try { - await this.workspaceService.resolveBaseImage(ctx, user, image); - return ImageAccessibility.Accessible; - } catch (e) { - console.log("failed to resolve image", { image }); - console.error(e); - throw new ApplicationError(ErrorCodes.INTERNAL_SERVER_ERROR, e.toString()); - } - } - async getOnboardingState(ctx: TraceContext): Promise { // Find useful details about the state of the Gitpod installation. const { rows } = await this.teamDB.findTeams( diff --git a/components/server/src/workspace/workspace-starter.ts b/components/server/src/workspace/workspace-starter.ts index 170f3538106f9b..f4794e88e88ac6 100644 --- a/components/server/src/workspace/workspace-starter.ts +++ b/components/server/src/workspace/workspace-starter.ts @@ -265,16 +265,14 @@ export class WorkspaceStarter { } if (options.forceDefaultImage) { - const req = new ResolveBaseImageRequest(); - req.setRef(this.config.workspaceDefaults.workspaceImage); - const allowAll = new BuildRegistryAuthTotal(); - allowAll.setAllowAll(true); - const auth = new BuildRegistryAuth(); - auth.setTotal(allowAll); - req.setAuth(auth); - - const client = await this.getImageBuilderClient(user, workspace, undefined, options?.region); - const res = await client.resolveBaseImage({ span }, req); + const res = await this.resolveBaseImage( + { span }, + user, + this.config.workspaceDefaults.workspaceImage, + workspace, + undefined, + options.region, + ); workspace.imageSource = { baseImageResolved: res.getRef(), }; @@ -1954,7 +1952,14 @@ export class WorkspaceStarter { return this.imagebuilderClientProvider.getClient(user, workspace, instance, region); } - public async resolveBaseImage(ctx: TraceContext, user: User, imageRef: string) { + public async resolveBaseImage( + ctx: TraceContext, + user: User, + imageRef: string, + workspace?: Workspace, + instance?: WorkspaceInstance, + region?: WorkspaceRegion, + ) { const req = new ResolveBaseImageRequest(); req.setRef(imageRef); const allowAll = new BuildRegistryAuthTotal(); @@ -1962,7 +1967,7 @@ export class WorkspaceStarter { const auth = new BuildRegistryAuth(); auth.setTotal(allowAll); req.setAuth(auth); - const client = await this.getImageBuilderClient(user); + const client = await this.getImageBuilderClient(user, workspace, instance, region); return client.resolveBaseImage({ span: ctx.span }, req); }