Skip to content

Commit e5bba0f

Browse files
svenefftingealedbf
andauthored
[server] extract tested usage-service (#18260)
* [server] extract tested usage-service * Fix runner removal script --------- Co-authored-by: Alejandro de Brito Fontes <[email protected]>
1 parent 5b1fbd3 commit e5bba0f

21 files changed

+603
-489
lines changed

.github/workflows/remove_runner.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
3232
gcloud auth activate-service-account --key-file ${{ steps.auth.outputs.credentials_file_path }}
3333
if [ -z "$(gcloud compute instances list | grep "${{ inputs.runner-label }}")" ]; then
34-
// vm is gone
34+
# vm is gone
3535
exit 0
3636
fi
3737

components/server/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@
1616
"clean": "rimraf dist",
1717
"clean:node": "rimraf node_modules",
1818
"purge": "yarn clean && yarn clean:node && yarn run rimraf yarn.lock",
19-
"test": ". $(leeway run components/gitpod-db:db-test-env) && yarn test:unit && yarn start-services && yarn test:db && yarn stop-services",
19+
"test": "cleanup() { echo 'Cleanup started'; yarn stop-services; }; trap cleanup EXIT; . $(leeway run components/gitpod-db:db-test-env) && yarn test:unit && yarn start-services && yarn test:db",
2020
"test:unit": "TS_NODE_FILES=true mocha --opts mocha.opts './**/*.spec.ts' --exclude './node_modules/**'",
2121
"test:db": "TS_NODE_FILES=true mocha --opts mocha.opts './**/*.spec.db.ts' --exclude './node_modules/**'",
2222
"start-services": "yarn start-testdb && yarn start-redis && yarn start-spicedb",
2323
"stop-services": "yarn stop-testdb && yarn stop-redis && yarn stop-spicedb",
2424
"start-testdb": "leeway run components/gitpod-db:init-testdb",
2525
"stop-testdb": "docker stop test-mysql || true && docker rm test-mysql || true",
26-
"start-spicedb": "if netstat -tuln | grep ':50051 '; then echo '`spicedb serve-testing` is already running.'; else spicedb serve-testing --load-configs components-spicedb--lib/schema/schema.yaml & fi",
27-
"stop-spicedb": "PID=$(lsof -t -i:50051); if [ -z \"$PID\" ]; then echo '`spicedb serve-testing` is not running'; else kill -INT $PID && echo 'Stopped `spicedb serve-testing`'; fi",
28-
"start-redis": "if netstat -tuln | grep ':6379 '; then echo 'Redis is already running.'; else docker run --name test-redis -p 6379:6379 -d redis; fi",
29-
"stop-redis": "docker stop test-redis || true && docker stop test-redis && docker rm test-redis",
26+
"start-spicedb": "leeway run components/spicedb:start-spicedb",
27+
"stop-spicedb": "leeway run components/spicedb:stop-spicedb",
28+
"start-redis": "if netstat -tuln | grep ':6379 '; then echo 'Redis is already running.'; else docker run --rm --name test-redis -p 6379:6379 -d redis; fi",
29+
"stop-redis": "docker stop test-redis || true",
3030
"telepresence": "telepresence --swap-deployment server --method inject-tcp --run yarn start-inspect"
3131
},
3232
"files": [

components/server/src/authorization/authorizer.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from "./definitions";
1818
import { SpiceDBAuthorizer } from "./spicedb-authorizer";
1919
import { Organization, TeamMemberInfo, Project, TeamMemberRole } from "@gitpod/gitpod-protocol";
20+
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
2021

2122
@injectable()
2223
export class Authorizer {
@@ -40,6 +41,21 @@ export class Authorizer {
4041
return this.authorizer.check(req, { orgID: orgId });
4142
}
4243

44+
async checkOrgPermissionAndThrow(userId: string, permission: OrganizationPermission, orgId: string) {
45+
if (await this.hasPermissionOnOrganization(userId, permission, orgId)) {
46+
return;
47+
}
48+
// check if the user has read permission
49+
if ("read_info" === permission || !(await this.hasPermissionOnOrganization(userId, "read_info", orgId))) {
50+
throw new ApplicationError(ErrorCodes.NOT_FOUND, `Organization ${orgId} not found.`);
51+
}
52+
53+
throw new ApplicationError(
54+
ErrorCodes.PERMISSION_DENIED,
55+
`You do not have ${permission} on organization ${orgId}`,
56+
);
57+
}
58+
4359
async hasPermissionOnProject(userId: string, permission: ProjectPermission, project: Project): Promise<boolean> {
4460
const req = v1.CheckPermissionRequest.create({
4561
subject: subject("user", userId),

components/server/src/authorization/definitions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type OrganizationPermission =
3535
| "read_git_provider"
3636
| "write_git_provider"
3737
| "read_billing"
38-
| "write_billing";
38+
| "write_billing"
39+
| "write_billing_admin";
3940
export type ProjectPermission = "write_info" | "read_info" | "delete";
4041
export type Permission = OrganizationPermission;

components/server/src/authorization/spicedb-authorizer.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ export class SpiceDBAuthorizer {
2222
private client: SpiceDBClient,
2323
) {}
2424

25+
/**
26+
* @deprecated only for testing
27+
*/
28+
async logRelationships() {
29+
// const resources = await this.client?.readRelationships({});
30+
//log.info(JSON.stringify(resources, undefined, 2));
31+
}
32+
2533
async check(
2634
req: v1.CheckPermissionRequest,
2735
experimentsFields?: {

components/server/src/billing/billing-mode.spec.db.ts

Lines changed: 0 additions & 158 deletions
This file was deleted.

components/server/src/billing/billing-mode.ts

Lines changed: 14 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,31 @@
66

77
import { inject, injectable } from "inversify";
88

9-
import { Team, User } from "@gitpod/gitpod-protocol";
9+
import { User } from "@gitpod/gitpod-protocol";
1010
import { Config } from "../config";
1111
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
12-
import { TeamDB, UserDB } from "@gitpod/gitpod-db/lib";
13-
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
1412
import { CostCenter_BillingStrategy } from "@gitpod/usage-api/lib/usage/v1/usage.pb";
1513
import { UsageService } from "../orgs/usage-service";
1614

17-
export const BillingModes = Symbol("BillingModes");
18-
export interface BillingModes {
19-
getBillingMode(attributionId: AttributionId, now: Date): Promise<BillingMode>;
20-
getBillingModeForUser(user: User, now: Date): Promise<BillingMode>;
21-
getBillingModeForTeam(team: Team, now: Date): Promise<BillingMode>;
22-
}
23-
2415
/**
2516
* Decides on a per org (legcay: also per-user) basis which BillingMode to use: "none" or "usage-based"
2617
*/
2718
@injectable()
28-
export class BillingModesImpl implements BillingModes {
29-
@inject(Config) protected readonly config: Config;
30-
@inject(UsageService) protected readonly usageService: UsageService;
31-
@inject(TeamDB) protected readonly teamDB: TeamDB;
32-
@inject(UserDB) protected readonly userDB: UserDB;
19+
export class BillingModes {
20+
constructor(
21+
@inject(Config) private readonly config: Config,
22+
@inject(UsageService) private readonly usageService: UsageService,
23+
) {}
3324

34-
public async getBillingMode(attributionId: AttributionId, now: Date): Promise<BillingMode> {
35-
switch (attributionId.kind) {
36-
case "team":
37-
const team = await this.teamDB.findTeamById(attributionId.teamId);
38-
if (!team) {
39-
throw new Error(`Cannot find team with id '${attributionId.teamId}'!`);
40-
}
41-
return this.getBillingModeForTeam(team, now);
42-
default:
43-
throw new Error("Invalid attributionId.");
25+
public async getBillingMode(userId: string, organizationId: string, now: Date): Promise<BillingMode> {
26+
if (!this.config.enablePayment) {
27+
// Payment is not enabled. E.g. Dedicated
28+
return { mode: "none" };
4429
}
30+
31+
const { billingStrategy } = await this.usageService.getCostCenter(userId, organizationId);
32+
const paid = billingStrategy === CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE;
33+
return { mode: "usage-based", paid };
4534
}
4635

4736
async getBillingModeForUser(user: User, now: Date): Promise<BillingMode> {
@@ -55,15 +44,4 @@ export class BillingModesImpl implements BillingModes {
5544
mode: "usage-based",
5645
};
5746
}
58-
59-
async getBillingModeForTeam(team: Team, _now: Date): Promise<BillingMode> {
60-
if (!this.config.enablePayment) {
61-
// Payment is not enabled. E.g. Dedicated
62-
return { mode: "none" };
63-
}
64-
65-
const billingStrategy = await this.usageService.getCurrentBillingStategy(AttributionId.create(team));
66-
const paid = billingStrategy === CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE;
67-
return { mode: "usage-based", paid };
68-
}
6947
}

components/server/src/billing/entitlement-service-ubp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export class EntitlementServiceUBP implements EntitlementService {
125125
// Member of paid team?
126126
const teams = await this.teamDB.findTeamsByUser(user.id);
127127
const isTeamSubscribedPromises = teams.map(async (team: Team) => {
128-
const billingStrategy = await this.usageService.getCurrentBillingStategy(AttributionId.create(team));
128+
const { billingStrategy } = await this.usageService.getCostCenter(user.id, team.id);
129129
return billingStrategy === CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE;
130130
});
131131
// Return the first truthy promise, or false if all the promises were falsy.

components/server/src/container-module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import { LoginCompletionHandler } from "./auth/login-completion-handler";
5353
import { VerificationService } from "./auth/verification-service";
5454
import { Authorizer } from "./authorization/authorizer";
5555
import { SpiceDBClient, spicedbClientFromEnv } from "./authorization/spicedb";
56-
import { BillingModes, BillingModesImpl } from "./billing/billing-mode";
56+
import { BillingModes } from "./billing/billing-mode";
5757
import { EntitlementService, EntitlementServiceImpl } from "./billing/entitlement-service";
5858
import { EntitlementServiceUBP } from "./billing/entitlement-service-ubp";
5959
import { BitbucketAppSupport } from "./bitbucket/bitbucket-app-support";
@@ -345,7 +345,7 @@ export const productionContainerModule = new ContainerModule(
345345
bind(EntitlementServiceUBP).toSelf().inSingletonScope();
346346
bind(EntitlementServiceImpl).toSelf().inSingletonScope();
347347
bind(EntitlementService).to(EntitlementServiceImpl).inSingletonScope();
348-
bind(BillingModes).to(BillingModesImpl).inSingletonScope();
348+
bind(BillingModes).toSelf().inSingletonScope();
349349

350350
// Periodic jobs
351351
bind(WorkspaceGarbageCollector).toSelf().inSingletonScope();

0 commit comments

Comments
 (0)