Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions lib/cloud-api/secrets.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,31 @@ export async function getSecret(projectId, secretName, accessToken) {
* @param {string} projectId - GCP project ID.
* @param {string} secretName - Name of the secret to create.
* @param {string} accessToken - OAuth2 access token.
* @param {Object.<string, string>} [labels] - Optional labels to apply to the secret.
Comment thread
harish-sharma-94 marked this conversation as resolved.
* @param {Function} progressCallback - Progress reporting callback.
* @returns {Promise<Object>} The created secret.
*/
export async function createSecret(
projectId,
secretName,
accessToken,
labels,
progressCallback
) {
const secretManager = await getSecretManagerClient(projectId, accessToken);
await logAndProgress(`Creating secret '${secretName}'...`, progressCallback);
const secret = {
replication: {
automatic: {},
},
};
if (labels) {
secret.labels = labels;
}
const [res] = await secretManager.createSecret({
parent: `projects/${projectId}`,
secretId: secretName,
secret: {
replication: {
automatic: {},
},
},
secret: secret,
});
return res;
}
Expand Down
8 changes: 7 additions & 1 deletion lib/cloud-api/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { logAndProgress } from '../util/helpers.js';
* @param {string} bucketName - The name of the storage bucket.
* @param {string} [location] - The location to create the bucket in if it doesn't exist.
* @param {string} [accessToken] - Optional access token.
* @param {Object.<string, string>} [labels] - Optional labels to apply to the bucket.
* @param {function(object): void} [progressCallback] - Optional callback for progress updates.
* @returns {Promise<import('@google-cloud/storage').Bucket>} A promise that resolves with the GCS Bucket object.
* @throws {Error} If there's an error checking or creating the bucket.
Expand All @@ -36,6 +37,7 @@ export async function ensureStorageBucketExists(
bucketName,
location,
accessToken,
labels,
progressCallback
) {
const storage = await getStorageClient(projectId, accessToken);
Expand All @@ -58,8 +60,12 @@ export async function ensureStorageBucketExists(
progressCallback
);
try {
const options = { location: location };
if (labels) {
options.metadata = { labels: labels };
}
const [createdBucket] = await callWithRetry(
() => storage.createBucket(bucketName, { location: location }),
() => storage.createBucket(bucketName, options),
`storage.createBucket ${bucketName}`
);
await logAndProgress(
Expand Down
23 changes: 16 additions & 7 deletions lib/deployment/compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import { execFile } from 'child_process';
import crypto from 'crypto';
import { logAndProgress } from '../util/helpers.js';
import { uploadDirectory } from './helpers.js';
import { TEMP_PATHS } from './constants.js';
import { TEMP_PATHS, RUN_COMPOSE } from './constants.js';
import { ensureRepositoryDownloaded } from '../util/artifacts.js';
import {
ensureStorageBucketExists,
uploadToStorageBucket,
grantBucketAccess,
} from '../cloud-api/storage.js';
import { getProjectNumber } from '../util/helpers.js';
import { getProjectNumber, sanitizeLabelValue } from '../util/helpers.js';
import { ensureApisEnabled } from '../cloud-api/helpers.js';
import {
getSecret,
Expand All @@ -40,9 +40,6 @@ import {

const execFileAsync = util.promisify(execFile);

const RUN_COMPOSE_BIN = 'run-compose';
const RUN_COMPOSE_VERSION = '1.0.0';

// TODO: Move to production project
const AR_PROJECT = 'serverless-runtimes-qa';
const AR_LOCATION = 'us-central1';
Expand Down Expand Up @@ -109,15 +106,15 @@ export async function runCompose(accessToken, progressCallback) {
}

const arch = ARCH_MAPPING[key];
const binPath = path.join(binDir, RUN_COMPOSE_BIN);
const binPath = path.join(binDir, RUN_COMPOSE.BIN);

const binPathResult = await ensureRepositoryDownloaded(
binPath,
{
project: AR_PROJECT,
location: AR_LOCATION,
repository: AR_REPOSITORY,
artifactPath: `${arch}:${RUN_COMPOSE_VERSION}:${RUN_COMPOSE_BIN}`,
artifactPath: `${arch}:${RUN_COMPOSE.VERSION}:${RUN_COMPOSE.BIN}`,
displayName: 'run-compose',
},
accessToken,
Expand Down Expand Up @@ -293,12 +290,18 @@ export async function composeVolumes(

const computeServiceAccount = `serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`;

const labels = {
[RUN_COMPOSE.LABEL_MANAGED_BY]: RUN_COMPOSE.LABEL_MANAGED_BY_VALUE,
[RUN_COMPOSE.LABEL_PROJECT]: sanitizeLabelValue(projectName),
};

// Explicitly ensure the bucket exists in the project
const bucket = await ensureStorageBucketExists(
projectId,
bucketName,
region,
accessToken,
labels,
progressCallback
);

Expand Down Expand Up @@ -439,6 +442,11 @@ export async function composeSecrets(

const projectNumber = await getProjectNumber(projectId, accessToken);
const computeServiceAccount = `${projectNumber}-compute@developer.gserviceaccount.com`;
const projectName = resourcesConfig.project;
const labels = {
[RUN_COMPOSE.LABEL_MANAGED_BY]: RUN_COMPOSE.LABEL_MANAGED_BY_VALUE,
[RUN_COMPOSE.LABEL_PROJECT]: sanitizeLabelValue(projectName),
};

const secretEntries = Object.entries(resourcesConfig.secrets);

Expand Down Expand Up @@ -471,6 +479,7 @@ export async function composeSecrets(
projectId,
secretId,
accessToken,
labels,
progressCallback
);
}
Expand Down
8 changes: 8 additions & 0 deletions lib/deployment/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,11 @@ export const RUNTIMES = {
};

export const MAX_ALLOWED_DIRECT_SOURCE_SIZE_BYTES = 250 * 1024 * 1024; // 250 MiB

export const RUN_COMPOSE = {
BIN: 'run-compose',
VERSION: '1.0.0',
LABEL_MANAGED_BY: 'managed-by',
LABEL_MANAGED_BY_VALUE: 'runcompose',
LABEL_PROJECT: 'run-compose-project',
};
21 changes: 21 additions & 0 deletions lib/util/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,24 @@ export function sanitizeCloudRunServiceName(name) {
// Cap at 49 characters (Cloud Run limit is usually 50-63 depending on API, 49 is safe)
return sanitized.slice(0, 49).replace(/-$/, '');
}

/**
* Sanitizes a string to be a valid GCP label value.
* Label values must be:
* - <= 63 characters.
* - Contain only lowercase letters, numbers, underscores, and dashes.
* @param {string} value - The string to sanitize.
* @returns {string} A valid GCP label value string.
*/
export function sanitizeLabelValue(value) {
if (value === undefined || value === null) {
return '';
}
const stringValue = String(value);
if (stringValue === '') {
return '';
}
let sanitized = stringValue.toLowerCase();
sanitized = sanitized.replace(/[^a-z0-9_-]/g, '-');
return sanitized.slice(0, 63);
}
Comment thread
harish-sharma-94 marked this conversation as resolved.
30 changes: 30 additions & 0 deletions test/local/cloud-api/secrets.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ describe('secrets.js', () => {
projectId,
secretName,
accessToken,
undefined,
() => {}
);

Expand All @@ -140,6 +141,35 @@ describe('secrets.js', () => {
},
});
});

it('should create a secret with labels successfully', async () => {
const mockSecret = {
name: `projects/${projectId}/secrets/${secretName}`,
};
createSecretImpl = () => Promise.resolve([mockSecret]);
const labels = { key: 'value' };

const result = await secrets.createSecret(
projectId,
secretName,
accessToken,
labels,
() => {}
);

assert.deepEqual(result, mockSecret);
assert.equal(createSecretMock.mock.callCount(), 1);
assert.deepEqual(createSecretMock.mock.calls[0].arguments[0], {
parent: `projects/${projectId}`,
secretId: secretName,
secret: {
replication: {
automatic: {},
},
labels: labels,
},
});
});
});

describe('addSecretVersion', () => {
Expand Down
Loading
Loading