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
20 changes: 8 additions & 12 deletions src/lib/components/deployments/compute-badge.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<script lang="ts">
import {
COMPUTE_PROVIDERS,
computeProviderFromType,
} from '$lib/components/workers/serverless-worker-form/compute-providers';
import Tooltip from '$lib/holocene/tooltip.svelte';
import { IconAwsColor, type IconComponent, IconGcpColor } from '$lib/io/icon';
import type { ComputeStatus } from '$lib/types/deployments';
import {
connectionStateColor,
Expand All @@ -9,20 +12,13 @@
deriveConnectionStatus,
} from '$lib/utilities/connection-status';

const CONFIG: Record<string, { Icon: IconComponent; label: string }> = {
// Brand marks, not monochrome glyphs: these identify a vendor, so they
// keep their own colour on either theme.
'aws-lambda': { Icon: IconAwsColor, label: 'Lambda' },
'aws-agentcore': { Icon: IconAwsColor, label: 'AgentCore' },
'gcp-cloud-run': { Icon: IconGcpColor, label: 'Cloud Run' },
};

let {
type,
computeStatus,
}: { type: string | undefined; computeStatus?: ComputeStatus } = $props();

const config = $derived(type ? CONFIG[type] : undefined);
const provider = $derived(computeProviderFromType(type));
const config = $derived(provider ? COMPUTE_PROVIDERS[provider] : undefined);
const state = $derived(
computeStatus ? deriveConnectionStatus(computeStatus) : undefined,
);
Expand All @@ -33,9 +29,9 @@
class="inline-flex min-w-24 items-center justify-center gap-2 border border-primary px-1"
>
{#if config}
{@const ProviderIcon = config.Icon}
{@const ProviderIcon = config.icon}
<ProviderIcon />
<p>{config.label}</p>
<p>{config.badgeLabel}</p>
{/if}
{#if state}
<span
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,20 @@
hasCloudRunImpersonatorPlaceholder,
interpolateCloudRunTerraformTemplate,
} from './cloud-run-terraform';
import {
COMPUTE_PROVIDERS,
computeProviderFromType,
type ComputeProviderTemplates,
type ComputeProviderValue,
defaultComputeProviderTemplates,
TERRAFORM_MODULES,
} from './compute-providers';
import { GCP_REGIONS } from './gcp-regions';
import defaultAgentCoreTerraformTemplate from './serverless-worker-agentcore.tf?raw';
import defaultCloudRunTerraformTemplate from './serverless-worker-cloud-run.tf?raw';
import defaultTerraformTemplate from './serverless-worker-lambda.tf?raw';
import {
defaultScaleDownStabilization,
interpolateTerraformTemplate,
scaleDownStabilizationUnits,
} from './shared';
import agentCoreCfnTemplate from './temporal-agentcore-role.yaml?raw';
import cfnTemplate from './temporal-worker-role.yaml?raw';

interface Props {
provider?: string;
Expand All @@ -54,13 +57,14 @@
scaleUpBacklogThreshold?: number;
maxWorkerLifetimeMs?: number;
metricsPollIntervalMs?: number;
cfnTemplateUrl?: string;
cfnTemplate?: string;
terraformTemplate?: string;
cloudRunTerraformTemplate?: string;
agentCoreCfnTemplateUrl?: string;
agentCoreCfnTemplate?: string;
agentCoreTerraformTemplate?: string;
/**
* The templates offered per provider. Defaults to the ones bundled here,
* which is what a self-hosted deployment wants. A consumer overrides them
* by passing a full map: it is exhaustive over ComputeProviderValue, so
* adding a provider fails the consumer's type check until it supplies
* templates for it.
*/
templates?: Record<ComputeProviderValue, ComputeProviderTemplates>;
errors?: {
lambdaArn?: string[];
agentCoreEndpointArn?: string[];
Expand Down Expand Up @@ -102,45 +106,44 @@
scaleUpBacklogThreshold = $bindable(),
maxWorkerLifetimeMs = $bindable(),
metricsPollIntervalMs = $bindable(),
cfnTemplateUrl,
cfnTemplate: cfnTemplateProp,
terraformTemplate,
cloudRunTerraformTemplate,
agentCoreCfnTemplateUrl,
agentCoreCfnTemplate: agentCoreCfnTemplateProp,
agentCoreTerraformTemplate,
templates = defaultComputeProviderTemplates,
errors = {},
}: Props = $props();

// Both AWS providers assume a role, and the role each needs is different:
// one grants lambda:InvokeFunction, the other bedrock-agentcore:
// InvokeAgentRuntime. Handing out the Lambda role for AgentCore would
// produce a role that cannot invoke a runtime, so the helper follows the
// produce a role that cannot invoke a runtime, so the templates follow the
// selected provider rather than being shared.
const isAgentCore = $derived(provider === 'agentcore');

const resolvedCfnTemplate = $derived(
isAgentCore
? (agentCoreCfnTemplateProp ?? agentCoreCfnTemplate)
: (cfnTemplateProp ?? cfnTemplate),
// `provider` arrives as a form field, so it is a string until proven one of
// ours. Narrowing here rather than asserting keeps an unrecognised value from
// reading as a provider that happens to have no entry.
const providerValue = $derived(computeProviderFromType(provider));
const providerTemplates = $derived(
providerValue ? templates[providerValue] : undefined,
);
const cloudFormation = $derived(providerTemplates?.cloudFormation);

const resolvedCfnTemplate = $derived(cloudFormation?.template ?? '');
// Only the key belonging to this provider's template is present, so both
// ARNs are passed and the absent one is a no-op.
const resolvedTerraformTemplate = $derived(
isAgentCore
? interpolateTerraformTemplate(
agentCoreTerraformTemplate ?? defaultAgentCoreTerraformTemplate,
{ externalId: roleExternalId, agentCoreEndpointArn },
)
: interpolateTerraformTemplate(
terraformTemplate ?? defaultTerraformTemplate,
{ externalId: roleExternalId, lambdaArn },
),
interpolateTerraformTemplate(providerTemplates?.terraform ?? '', {
externalId: roleExternalId,
lambdaArn,
agentCoreEndpointArn,
}),
);
const terraformModuleHref = $derived(
`https://github.com/temporalio/terraform-modules/tree/main/modules/serverless-workers/aws/${isAgentCore ? 'agentcore' : 'lambda'}`,
(providerValue && COMPUTE_PROVIDERS[providerValue].terraformModuleHref) ||
TERRAFORM_MODULES,
);
// Read from the map rather than the selected provider: the Cloud Run block
// renders inside its own branch, where `provider` is already 'cloud-run'.
const resolvedCloudRunTerraformTemplate = $derived(
interpolateCloudRunTerraformTemplate(
cloudRunTerraformTemplate ?? defaultCloudRunTerraformTemplate,
templates['cloud-run'].terraform,
gcpProject,
),
);
Expand All @@ -149,7 +152,7 @@
);

const launchStackHref = $derived.by(() => {
const templateUrl = isAgentCore ? agentCoreCfnTemplateUrl : cfnTemplateUrl;
const templateUrl = cloudFormation?.url;

if (!templateUrl) {
return 'https://console.aws.amazon.com/cloudformation/';
Expand Down Expand Up @@ -181,9 +184,7 @@
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = isAgentCore
? 'temporal-agentcore-role.yaml'
: 'temporal-worker-role.yaml';
a.download = cloudFormation?.fileName ?? 'temporal-worker-role.yaml';
a.click();
URL.revokeObjectURL(url);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
hasCloudRunImpersonatorPlaceholder,
interpolateCloudRunTerraformTemplate,
} from './cloud-run-terraform';
import { defaultComputeProviderTemplates } from './compute-providers';
import defaultAgentCoreTerraformTemplate from './serverless-worker-agentcore.tf?raw';
import defaultCloudRunTerraformTemplate from './serverless-worker-cloud-run.tf?raw';
import defaultTerraformTemplate from './serverless-worker-lambda.tf?raw';
Expand Down Expand Up @@ -89,6 +90,7 @@ interface ComputeFieldsOptions {
agentCoreEndpointArn?: string;
gcpProject?: string;
gcpServiceAccount?: string;
/** Overrides the Terraform template for the named provider only. */
cloudRunTerraformTemplate?: string;
terraformTemplate?: string;
roleExternalId?: string;
Expand All @@ -106,6 +108,26 @@ const renderComputeFields = ({
terraformTemplate,
roleExternalId = '',
}: ComputeFieldsOptions): string => {
// The component takes one exhaustive map now, so an override for a single
// provider is layered onto the defaults rather than passed as its own prop.
const templates = {
...defaultComputeProviderTemplates,
...(terraformTemplate !== undefined && {
[provider]: {
...defaultComputeProviderTemplates[
provider as keyof typeof defaultComputeProviderTemplates
],
terraform: terraformTemplate,
},
}),
...(cloudRunTerraformTemplate !== undefined && {
'cloud-run': {
...defaultComputeProviderTemplates['cloud-run'],
terraform: cloudRunTerraformTemplate,
},
}),
};

const { body } = renderComponent(computeFields, {
props: {
provider,
Expand All @@ -115,8 +137,7 @@ const renderComputeFields = ({
roleExternalId,
gcpProject,
gcpServiceAccount,
cloudRunTerraformTemplate,
terraformTemplate,
templates,
},
});
return body;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
import RadioGroup from '$lib/holocene/radio-input/radio-group.svelte';
import { translate } from '$lib/i18n/translate';
import { Badge } from '$lib/io/badge';
import { IconAwsColor, type IconComponent, IconGcpColor } from '$lib/io/icon';

import {
type ComputeProviderOption,
COMPUTE_PROVIDER_VALUES,
COMPUTE_PROVIDERS,
type ComputeProviderReleaseStage,
type ComputeProviderValue,
defaultReleaseStage,
} from './shared';
} from './compute-providers';
import { type ComputeProviderOption, defaultReleaseStage } from './shared';

interface Props {
provider?: string;
Expand All @@ -26,35 +26,11 @@

const configuredProviders = untrack(() => providers);

// The brand marks, not the monochrome glyphs. These are vendor logos rather
// than UI icons, so they keep their own colour on either theme.
const providerIcon: Record<ComputeProviderValue, IconComponent> = {
lambda: IconAwsColor,
agentcore: IconAwsColor,
'cloud-run': IconGcpColor,
};
const providerLabel = (value: ComputeProviderValue): string =>
translate(COMPUTE_PROVIDERS[value].labelKey);

const providerLabel = (value: ComputeProviderValue): string => {
switch (value) {
case 'lambda':
return translate('workers.provider-lambda');
case 'agentcore':
return translate('workers.provider-agentcore');
case 'cloud-run':
return translate('workers.provider-cloud-run');
}
};

const providerDescription = (value: ComputeProviderValue): string => {
switch (value) {
case 'lambda':
return translate('workers.provider-lambda-description');
case 'agentcore':
return translate('workers.provider-agentcore-description');
case 'cloud-run':
return translate('workers.provider-cloud-run-description');
}
};
const providerDescription = (value: ComputeProviderValue): string =>
translate(COMPUTE_PROVIDERS[value].descriptionKey);

const releaseStageOf = (
option: ComputeProviderOption,
Expand Down Expand Up @@ -82,11 +58,9 @@
* how Temporal Cloud offers only the providers matching the Namespace's own
* cloud.
*/
const defaultProviders = $derived<ComputeProviderOption[]>([
{ value: 'lambda' },
{ value: 'agentcore' },
{ value: 'cloud-run' },
]);
const defaultProviders = $derived<ComputeProviderOption[]>(
COMPUTE_PROVIDER_VALUES.map((value) => ({ value })),
);

const resolvedProviders = $derived(configuredProviders ?? defaultProviders);
const visibleProviders = $derived(
Expand Down Expand Up @@ -130,7 +104,7 @@
</span>
{/snippet}
{#snippet icon()}
{@const ProviderIcon = providerIcon[option.value]}
{@const ProviderIcon = COMPUTE_PROVIDERS[option.value].icon}
<div
class="flex h-11 w-11 items-center justify-center rounded border border-primary bg-surface-primary"
>
Expand Down
Loading
Loading