Skip to content
Open
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
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ jobs:
run: 'curl "${{ steps.deploy.outputs.url }}"'
```

### Direct VPC Egress (2nd gen)

```yaml
- name: 'Deploy Cloud Function with Direct VPC Egress'
uses: 'google-github-actions/deploy-cloud-functions@v4'
with:
name: 'my-function'
runtime: 'nodejs22'
network: 'default'
subnet: 'default'
network_tags: 'internal-egress,db-client'
direct_vpc_egress: 'private-ranges-only'
```

## Inputs

> [!IMPORTANT]
Expand Down Expand Up @@ -188,6 +202,11 @@ jobs:
- <a name="__input_cpu"></a><a href="#user-content-__input_cpu"><code>cpu</code></a>: _(Optional)_ The number of available CPUs to set (e.g. 0.5, 2, 2000m). By default, a
new function's available CPUs is determined based on its memory value.

- <a name="__input_direct_vpc_egress"></a><a href="#user-content-__input_direct_vpc_egress"><code>direct_vpc_egress</code></a>: _(Optional)_ Egress settings that control what outbound traffic is diverted through
the Direct VPC network. Allowed values are "PRIVATE_RANGES_ONLY" and
"ALL_TRAFFIC" (or "private-ranges-only" and "all-traffic"). Defaults to
"PRIVATE_RANGES_ONLY" when Direct VPC network or subnet is specified.

- <a name="__input_memory"></a><a href="#user-content-__input_memory"><code>memory</code></a>: _(Optional)_ The amount of memory available for the function to use. Allowed values are
of the format: <number><unit> with allowed units of "k", "M", "G", "Ki",
"Mi", "Gi" (e.g 128M, 10Mb, 1024Gi).
Expand Down Expand Up @@ -228,6 +247,13 @@ jobs:
- <a name="__input_min_instance_count"></a><a href="#user-content-__input_min_instance_count"><code>min_instance_count</code></a>: _(Optional)_ Sets the minimum number of instances for the function. This is helpful for
reducing cold start times.

- <a name="__input_network"></a><a href="#user-content-__input_network"><code>network</code></a>: _(Optional)_ The VPC network name or fully-qualified identifier to which the function
will be connected via Direct VPC Egress (Cloud Functions 2nd gen only).
Specify network, subnet, or both. Mutually exclusive with `vpc_connector`.

- <a name="__input_network_tags"></a><a href="#user-content-__input_network_tags"><code>network_tags</code></a>: _(Optional)_ Comma-separated list of Compute Engine network tags to apply to Direct VPC
egress traffic (e.g. `tag-a,tag-b`).

- <a name="__input_secrets"></a><a href="#user-content-__input_secrets"><code>secrets</code></a>: _(Optional)_ List of KEY=VALUE pairs to use as secrets. These are comma-separated or
newline-separated `KEY=VALUE`. Keys or values that contain separators must
be escaped with a backslash (e.g. `\,` or `\\n`) unless quoted. Any
Expand Down Expand Up @@ -267,6 +293,11 @@ jobs:
- <a name="__input_service_timeout"></a><a href="#user-content-__input_service_timeout"><code>service_timeout</code></a>: _(Optional, default: `60s`)_ The function execution timeout, specified as a time duration (e.g. "30s"
for 30 seconds).

- <a name="__input_subnet"></a><a href="#user-content-__input_subnet"><code>subnet</code></a>: _(Optional)_ The VPC subnetwork name or fully-qualified identifier from which the
function will obtain IP addresses via Direct VPC Egress (Cloud Functions
2nd gen only). Specify network, subnet, or both. Mutually exclusive with
`vpc_connector`.

- <a name="__input_vpc_connector"></a><a href="#user-content-__input_vpc_connector"><code>vpc_connector</code></a>: _(Optional)_ ID of the connector or fully qualified identifier for the connector.

- <a name="__input_vpc_connector_egress_settings"></a><a href="#user-content-__input_vpc_connector_egress_settings"><code>vpc_connector_egress_settings</code></a>: _(Optional, default: `PRIVATE_RANGES_ONLY`)_ Egress settings controls what traffic is diverted through the VPC Access
Expand Down
29 changes: 29 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,14 @@ inputs:
new function's available CPUs is determined based on its memory value.
required: false

direct_vpc_egress:
description: |-
Egress settings that control what outbound traffic is diverted through
the Direct VPC network. Allowed values are "PRIVATE_RANGES_ONLY" and
"ALL_TRAFFIC" (or "private-ranges-only" and "all-traffic"). Defaults to
"PRIVATE_RANGES_ONLY" when Direct VPC network or subnet is specified.
required: false

memory:
description: |-
The amount of memory available for the function to use. Allowed values are
Expand Down Expand Up @@ -272,6 +280,19 @@ inputs:
reducing cold start times.
required: false

network:
description: |-
The VPC network name or fully-qualified identifier to which the function
will be connected via Direct VPC Egress (Cloud Functions 2nd gen only).
Specify network, subnet, or both. Mutually exclusive with `vpc_connector`.
required: false

network_tags:
description: |-
Comma-separated list of Compute Engine network tags to apply to Direct VPC
egress traffic (e.g. `tag-a,tag-b`).
required: false

secrets:
description: |-
List of KEY=VALUE pairs to use as secrets. These are comma-separated or
Expand Down Expand Up @@ -321,6 +342,14 @@ inputs:
default: '60s'
required: false

subnet:
description: |-
The VPC subnetwork name or fully-qualified identifier from which the
function will obtain IP addresses via Direct VPC Egress (Cloud Functions
2nd gen only). Specify network, subnet, or both. Mutually exclusive with
`vpc_connector`.
required: false

vpc_connector:
description: |-
ID of the connector or fully qualified identifier for the connector.
Expand Down
16 changes: 8 additions & 8 deletions dist/main/index.js

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,17 @@ export enum VpcConnectorEgressSettings {
ALL_TRAFFIC = 'ALL_TRAFFIC',
}

export enum DirectVpcEgress {
VPC_EGRESS_PRIVATE_RANGES_ONLY = 'VPC_EGRESS_PRIVATE_RANGES_ONLY',
VPC_EGRESS_ALL_TRAFFIC = 'VPC_EGRESS_ALL_TRAFFIC',
}

export type DirectVpcNetworkInterface = {
network?: string;
subnetwork?: string;
tags?: string[];
};

export enum IngressSettings {
ALLOW_ALL = 'ALLOW_ALL',
ALLOW_INTERNAL_ONLY = 'ALLOW_INTERNAL_ONLY',
Expand Down Expand Up @@ -177,6 +188,8 @@ export type CloudFunction = {
timeoutSeconds?: number;
vpcConnector?: string;
vpcConnectorEgressSettings?: VpcConnectorEgressSettings;
directVpcEgress?: DirectVpcEgress;
directVpcNetworkInterface?: DirectVpcNetworkInterface[];
};

eventTrigger?: {
Expand Down Expand Up @@ -635,6 +648,9 @@ export class CloudFunctionsClient {
if (cf.serviceConfig?.vpcConnector !== undefined) keys.push('serviceConfig.vpcConnector');
if (cf.serviceConfig?.vpcConnectorEgressSettings !== undefined)
keys.push('serviceConfig.vpcConnectorEgressSettings');
if (cf.serviceConfig?.directVpcEgress !== undefined) keys.push('serviceConfig.directVpcEgress');
if (cf.serviceConfig?.directVpcNetworkInterface !== undefined)
keys.push('serviceConfig.directVpcNetworkInterface');

if (cf.eventTrigger?.triggerRegion !== undefined) keys.push('eventTrigger.triggerRegion');
if (cf.eventTrigger?.eventType !== undefined) keys.push('eventTrigger.eventType');
Expand Down
23 changes: 22 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ import {
RetryPolicy,
VpcConnectorEgressSettings,
} from './client';
import { formatEntry, parseEventTriggerFilters, parseSecrets, stringToInt } from './util';
import {
formatEntry,
parseDirectVpc,
parseEventTriggerFilters,
parseSecrets,
stringToInt,
} from './util';

async function run() {
try {
Expand Down Expand Up @@ -88,6 +94,19 @@ async function run() {
VpcConnectorEgressSettings,
getInput('vpc_connector_egress_settings') || VpcConnectorEgressSettings.PRIVATE_RANGES_ONLY,
);
const network = presence(getInput('network'));
const subnet = presence(getInput('subnet'));
const networkTags = presence(getInput('network_tags'));
const directVpcEgress = presence(getInput('direct_vpc_egress'));

const { directVpcNetworkInterface, directVpcEgress: parsedDirectVpcEgress } = parseDirectVpc({
environment,
vpcConnector,
network,
subnet,
networkTags,
directVpcEgress,
});

// eventTrigger
const eventTriggerLocation = presence(getInput('event_trigger_location'));
Expand Down Expand Up @@ -146,6 +165,8 @@ async function run() {
timeoutSeconds: serviceTimeout,
vpcConnector: vpcConnector,
vpcConnectorEgressSettings: vpcConnectorEgressSettings,
directVpcEgress: parsedDirectVpcEgress,
directVpcNetworkInterface: directVpcNetworkInterface,
},

eventTrigger: {
Expand Down
94 changes: 93 additions & 1 deletion src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,21 @@ import * as path from 'path';

import * as Archiver from 'archiver';
import {
parseCSV,
parseGcloudIgnore,
parseKVString,
toPlatformPath,
} from '@google-github-actions/actions-utils';
import ignore from 'ignore';

import { EventFilter, SecretEnvVar, SecretVolume } from './client';
import {
DirectVpcEgress,
DirectVpcNetworkInterface,
Environment,
EventFilter,
SecretEnvVar,
SecretVolume,
} from './client';
import { SecretName } from './secret';

/**
Expand Down Expand Up @@ -225,3 +233,87 @@ export function parseSecrets(

return [secretEnvVars, secretVolumes];
}

/**
* DirectVpcInputs holds raw user inputs related to Direct VPC Egress.
*/
export type DirectVpcInputs = {
environment?: Environment;
vpcConnector?: string;
network?: string;
subnet?: string;
networkTags?: string;
directVpcEgress?: string;
};

/**
* DirectVpcConfig holds parsed Direct VPC configuration for CloudFunction.
*/
export type DirectVpcConfig = {
directVpcNetworkInterface?: DirectVpcNetworkInterface[];
directVpcEgress?: DirectVpcEgress;
};

/**
* parseDirectVpcEgress parses direct_vpc_egress input into DirectVpcEgress enum.
*/
export function parseDirectVpcEgress(val?: string): DirectVpcEgress | undefined {
if (!val) {
return undefined;
}
const normalized = val.trim().toUpperCase().replace(/-/g, '_');
if (normalized === 'ALL_TRAFFIC' || normalized === 'VPC_EGRESS_ALL_TRAFFIC') {
return DirectVpcEgress.VPC_EGRESS_ALL_TRAFFIC;
}
if (normalized === 'PRIVATE_RANGES_ONLY' || normalized === 'VPC_EGRESS_PRIVATE_RANGES_ONLY') {
return DirectVpcEgress.VPC_EGRESS_PRIVATE_RANGES_ONLY;
}
throw new Error(
`Invalid value '${val}' for 'direct_vpc_egress', valid values are 'all-traffic', 'private-ranges-only'`,
);
}

/**
* parseDirectVpc validates and parses Direct VPC inputs into DirectVpcConfig.
*/
export function parseDirectVpc(inputs: DirectVpcInputs): DirectVpcConfig {
const { environment, vpcConnector, network, subnet, networkTags, directVpcEgress } = inputs;
const hasDirectVpcSettings = Boolean(network || subnet || networkTags || directVpcEgress);

if (!hasDirectVpcSettings) {
return {};
}

if (environment === Environment.GEN_1) {
throw new Error(
`Direct VPC Egress is only supported on 2nd gen Cloud Functions (got environment: 'GEN_1')`,
);
}

if (vpcConnector) {
throw new Error(
`Cannot specify both 'vpc_connector' and Direct VPC settings ('network'/'subnet')`,
);
}

if (!network && !subnet) {
throw new Error(
`Cannot specify 'network_tags' or 'direct_vpc_egress' without specifying 'network' or 'subnet'`,
);
}

const tags = networkTags ? parseCSV(networkTags) : undefined;
const egress = directVpcEgress
? parseDirectVpcEgress(directVpcEgress)
: DirectVpcEgress.VPC_EGRESS_PRIVATE_RANGES_ONLY;

const directVpcInterface: DirectVpcNetworkInterface = {};
if (network) directVpcInterface.network = network;
if (subnet) directVpcInterface.subnetwork = subnet;
if (tags && tags.length > 0) directVpcInterface.tags = tags;

return {
directVpcNetworkInterface: [directVpcInterface],
directVpcEgress: egress,
};
}
Loading