Skip to content

Commit

Permalink
Merge pull request #491 from Infisical/railway
Browse files Browse the repository at this point in the history
Railway Integration + Service Accounts
  • Loading branch information
dangtony98 authored Apr 9, 2023
2 parents 5855c85 + 365daa9 commit 7e15e73
Show file tree
Hide file tree
Showing 160 changed files with 4,904 additions and 985 deletions.
151 changes: 149 additions & 2 deletions backend/src/controllers/v1/integrationAuthController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
revokeAccess
} from '../../integrations';
import {
INTEGRATION_VERCEL_API_URL
INTEGRATION_VERCEL_API_URL,
INTEGRATION_RAILWAY_API_URL
} from '../../variables';
import request from '../../config/request';

Expand Down Expand Up @@ -203,7 +204,8 @@ export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
}

/**
* Return list of available Vercel (preview) branches
* Return list of available Vercel (preview) branches for Vercel project with
* id [appId]
* @param req
* @param res
*/
Expand Down Expand Up @@ -246,6 +248,151 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
});
}

/**
* Return list of Railway environments for Railway project with
* id [appId]
* @param req
* @param res
*/
export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: Response) => {
const { integrationAuthId } = req.params;
const appId = req.query.appId as string;

interface RailwayEnvironment {
node: {
id: string;
name: string;
isEphemeral: boolean;
}
}

interface Environment {
environmentId: string;
name: string;
}

let environments: Environment[] = [];

if (appId && appId !== '') {
const query = `
query GetEnvironments($projectId: String!, $after: String, $before: String, $first: Int, $isEphemeral: Boolean, $last: Int) {
environments(projectId: $projectId, after: $after, before: $before, first: $first, isEphemeral: $isEphemeral, last: $last) {
edges {
node {
id
name
isEphemeral
}
}
}
}
`;

const variables = {
projectId: appId
}

const { data: { data: { environments: { edges } } } } = await request.post(INTEGRATION_RAILWAY_API_URL, {
query,
variables,
}, {
headers: {
'Authorization': `Bearer ${req.accessToken}`,
'Content-Type': 'application/json',
},
});

environments = edges.map((e: RailwayEnvironment) => {
return ({
name: e.node.name,
environmentId: e.node.id
});
});
}

return res.status(200).send({
environments
});
}

/**
* Return list of Railway services for Railway project with id
* [appId]
* @param req
* @param res
*/
export const getIntegrationAuthRailwayServices = async (req: Request, res: Response) => {
const { integrationAuthId } = req.params;
const appId = req.query.appId as string;

interface RailwayService {
node: {
id: string;
name: string;
}
}

interface Service {
name: string;
serviceId: string;
}

let services: Service[] = [];

const query = `
query project($id: String!) {
project(id: $id) {
createdAt
deletedAt
id
description
expiredAt
isPublic
isTempProject
isUpdatable
name
prDeploys
teamId
updatedAt
upstreamUrl
services {
edges {
node {
id
name
}
}
}
}
}
`;

if (appId && appId !== '') {
const variables = {
id: appId
}

const { data: { data: { project: { services: { edges } } } } } = await request.post(INTEGRATION_RAILWAY_API_URL, {
query,
variables
}, {
headers: {
'Authorization': `Bearer ${req.accessToken}`,
'Content-Type': 'application/json',
},
});

services = edges.map((e: RailwayService) => ({
name: e.node.name,
serviceId: e.node.id
}));
}

return res.status(200).send({
services
});
}

/**
* Delete integration authorization with id [integrationAuthId]
* @param req
Expand Down
8 changes: 7 additions & 1 deletion backend/src/controllers/v1/integrationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export const createIntegration = async (req: Request, res: Response) => {
isActive,
sourceEnvironment,
targetEnvironment,
targetEnvironmentId,
targetService,
targetServiceId,
owner,
path,
region
Expand All @@ -39,12 +42,15 @@ export const createIntegration = async (req: Request, res: Response) => {
app,
appId,
targetEnvironment,
targetEnvironmentId,
targetService,
targetServiceId,
owner,
path,
region,
integration: req.integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId)
}).save();
}).save();

if (integration) {
// trigger event - push secrets
Expand Down
8 changes: 4 additions & 4 deletions backend/src/controllers/v1/secretController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { pushKeys } from '../../helpers/key';
import { eventPushSecrets } from '../../events';
import { EventService } from '../../services';
import { getPostHogClient } from '../../services';
import { TelemetryService } from '../../services';

interface PushSecret {
ciphertextKey: string;
Expand Down Expand Up @@ -38,7 +38,7 @@ export const pushSecrets = async (req: Request, res: Response) => {
// upload (encrypted) secrets to workspace with id [workspaceId]

try {
const postHogClient = getPostHogClient();
const postHogClient = TelemetryService.getPostHogClient();
let { secrets }: { secrets: PushSecret[] } = req.body;
const { keys, environment, channel } = req.body;
const { workspaceId } = req.params;
Expand Down Expand Up @@ -112,7 +112,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
let secrets;
let key;
try {
const postHogClient = getPostHogClient();
const postHogClient = TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
Expand Down Expand Up @@ -181,7 +181,7 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
let secrets;
let key;
try {
const postHogClient = getPostHogClient();
const postHogClient = TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
Expand Down
1 change: 1 addition & 0 deletions backend/src/controllers/v2/apiKeyDataController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const createAPIKeyData = async (req: Request, res: Response) => {

apiKeyData = await new APIKeyData({
name,
lastUsed: new Date(),
expiresAt,
user: req.user._id,
secretHash
Expand Down
6 changes: 3 additions & 3 deletions backend/src/controllers/v2/environmentController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { SecretVersion } from '../../ee/models';
import { BadRequestError } from '../../utils/errors';
import _ from 'lodash';
import { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization';
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../variables';

/**
* Create new workspace environment named [environmentName] under workspace with id
Expand Down Expand Up @@ -244,8 +244,8 @@ export const getAllAccessibleEnvironmentsOfWorkspace = async (
throw BadRequestError()
}
relatedWorkspace.environments.forEach(environment => {
const isReadBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: ABILITY_READ })
const isWriteBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: ABILITY_WRITE })
const isReadBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: PERMISSION_READ_SECRETS })
const isWriteBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: PERMISSION_WRITE_SECRETS })
if (isReadBlocked && isWriteBlocked) {
return
} else {
Expand Down
2 changes: 2 additions & 0 deletions backend/src/controllers/v2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as serviceTokenDataController from './serviceTokenDataController';
import * as apiKeyDataController from './apiKeyDataController';
import * as secretController from './secretController';
import * as secretsController from './secretsController';
import * as serviceAccountsController from './serviceAccountsController';
import * as environmentController from './environmentController';
import * as tagController from './tagController';

Expand All @@ -20,6 +21,7 @@ export {
apiKeyDataController,
secretController,
secretsController,
serviceAccountsController,
environmentController,
tagController
}
68 changes: 39 additions & 29 deletions backend/src/controllers/v2/organizationsController.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import {
MembershipOrg,
Membership,
Workspace
Workspace,
ServiceAccount
} from '../../models';
import { deleteMembershipOrg } from '../../helpers/membershipOrg';
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
Expand Down Expand Up @@ -260,37 +262,45 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
}
}
*/
let workspaces;
try {
const { organizationId } = req.params;
const { organizationId } = req.params;

const workspacesSet = new Set(
(
await Workspace.find(
{
organization: organizationId
},
'_id'
)
).map((w) => w._id.toString())
);
const workspacesSet = new Set(
(
await Workspace.find(
{
organization: organizationId
},
'_id'
)
).map((w) => w._id.toString())
);

workspaces = (
await Membership.find({
user: req.user._id
}).populate('workspace')
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get organization workspaces'
});
}
const workspaces = (
await Membership.find({
user: req.user._id
}).populate('workspace')
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);

return res.status(200).send({
workspaces
});
}

/**
* Return service accounts for organization with id [organizationId]
* @param req
* @param res
*/
export const getOrganizationServiceAccounts = async (req: Request, res: Response) => {
const { organizationId } = req.params;

const serviceAccounts = await ServiceAccount.find({
organization: new Types.ObjectId(organizationId)
});

return res.status(200).send({
workspaces
serviceAccounts
});
}
Loading

1 comment on commit 7e15e73

@github-actions
Copy link

Choose a reason for hiding this comment

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

Coverage report for backend

St.
Category Percentage Covered / Total
🟢 Statements 90.63% 87/96
🔴 Branches 25% 2/8
🔴 Functions 50% 3/6
🟢 Lines 92.47% 86/93

Test suite run success

24 tests passing in 2 suites.

Report generated by 🧪jest coverage report action from 7e15e73

Please sign in to comment.