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
2 changes: 1 addition & 1 deletion config/components/service-catalog/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ spec:
producerProjectRef:
name: datum-cloud
enablementPolicy:
mode: GatedByProvider
mode: SelfService
phase: Published
serviceName: compute.datumapis.com
5 changes: 3 additions & 2 deletions docs/agent/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,9 @@ about them explicitly rather than defaulting them quietly.
Two things are outside your reach entirely. You cannot build or push a
container image: it must already exist in a registry, fully qualified, and be
built for the runtime Datum runs it on — `datumctl compute build` is the step
the customer runs. And you cannot enable Compute for a project or grant it
quota; both are Datum's to give.
the customer runs. These tools cannot enable Compute or change quota. The
customer can enable Compute with `datumctl services enable compute.datumapis.com`;
default quota is provisioned when the service becomes active.

Writing is a sequence, not a call. `compute_workload_render` turns inputs into
a Workload manifest and touches nothing. The rest is the platform's own change
Expand Down
13 changes: 7 additions & 6 deletions docs/agent/skills/workload-create.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ Two things you cannot do, however the request is phrased:

- **You cannot build or push an image.** The image has to exist in a registry
before any of this starts.
- **You cannot turn Compute on for a project, and you cannot grant it quota.**
Both are Datum's to grant. Say so and name the step the user takes.
- **You cannot turn Compute on for a project or change its quota with these tools.**
The user can enable Compute with `datumctl services enable compute.datumapis.com`.
Say so and name the step the user takes.

The project is fixed by the request that reached you. There is no tool argument
for it, so you cannot create a workload in a project other than the one the
Expand All @@ -32,10 +33,10 @@ different answer:

| Check | Tool | If it fails |
|---|---|---|
| Compute is offered to the project | `locations_list` with service `compute` | Nothing can be placed. Datum's to enable — the user runs `datumctl services enable compute.datumapis.com`, and approval is a manual step on Datum's side. |
| Compute is offered to the project | `locations_list` with service `compute` | Nothing can be placed. The user can enable Compute with `datumctl services enable compute.datumapis.com`. |
| Somewhere to run it | `locations_list` with service `compute` | The location names it returns are the only ones a placement may name. A location missing from the list is one compute is not offered in. An empty list means nothing is available to this project yet; that is Datum's, not something the user can add. |
| A network | `resources_list` for kind `Network` in `networking.datumapis.com/v1alpha` | `default` by convention. If the one the workload names is missing, add a Network manifest of that name to the same `resources_plan` call — the plan orders it ahead of the Workload. Say so when you show the plan, because it is a second object being created. |
| Quota | `quota_get` with service `compute.datumapis.com` | Quota is granted by Datum and cannot be self-served. A project with none can still create a workload; its instances then sit at `QuotaGranted=False` with `QuotaNoBudget` and never start. |
| Quota | `quota_get` with service `compute.datumapis.com` | Default quota is provisioned when Compute is enabled. If none appears, wait for provisioning; an instance created without it sits at `QuotaGranted=False` with `QuotaNoBudget` and cannot start. |

Do the quota arithmetic before you apply, not after. Replicas times the instance
type's size from `compute_instance_types_list`, against what `quota_get` says is
Expand Down Expand Up @@ -361,8 +362,8 @@ could not get a legitimate creation done:
Not gaps, however awkward the turn:

- **No image.** Building one was never in scope here.
- **No quota, or Compute not enabled.** Those are grants, and the tools
reporting them accurately is the tools working.
- **No quota, or Compute not enabled.** The user can enable Compute; default quota
follows activation. The tools reporting these states accurately is the tools working.
- **A rejection that was right.** An unsupported instance type or an unattached
volume is the plan doing its job — that is the answer, and it saved a broken
workload.
Expand Down
5 changes: 2 additions & 3 deletions internal/cmd/compute/util/activation.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ const ComputeServiceName = "compute.datumapis.com"
// catalog.
//
// The SDK reads the enablement mode and description from the live Service
// instead of a hand-written copy. Without the mode, the SDK treats compute as
// self-service. The gate would then submit access requests without asking,
// including from CI, even though compute requires provider approval.
// instead of a hand-written copy, so commands follow the published
// self-service policy and can enable Compute on first use.
func ResolveComputeService(ctx context.Context) (activation.ServiceInfo, error) {
cfg, err := restConfig(func(apiHost string) string { return "https://" + apiHost })
if err != nil {
Expand Down
12 changes: 7 additions & 5 deletions internal/cmd/compute/util/activation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import (

// TestComputeServiceResolvesFromPublishedManifest checks the gate's lookup
// against the Service manifest compute publishes. If the name drifts from the
// manifest, every gated command fails. If the manifest loses its enablement
// policy, the gate treats compute as self-service and submits access requests
// without asking.
// manifest, every gated command fails. Keep the explicit self-service policy
// in sync with the service we publish.
func TestComputeServiceResolvesFromPublishedManifest(t *testing.T) {
raw, err := os.ReadFile("../../../../config/components/service-catalog/service.yaml")
if err != nil {
Expand All @@ -33,8 +32,11 @@ func TestComputeServiceResolvesFromPublishedManifest(t *testing.T) {
if info.ObjectName != "compute" {
t.Errorf("ObjectName = %q, want %q", info.ObjectName, "compute")
}
if info.EnablementMode != servicesv1alpha1.EnablementModeGatedByProvider {
t.Errorf("EnablementMode = %q, want %q", info.EnablementMode, servicesv1alpha1.EnablementModeGatedByProvider)
if svc.Spec.EnablementPolicy == nil || svc.Spec.EnablementPolicy.Mode != servicesv1alpha1.EnablementModeSelfService {
t.Fatalf("published Compute service must explicitly enable self-service")
}
if info.EnablementMode != servicesv1alpha1.EnablementModeSelfService {
t.Errorf("EnablementMode = %q, want %q", info.EnablementMode, servicesv1alpha1.EnablementModeSelfService)
}
}

Expand Down
28 changes: 12 additions & 16 deletions ui/consumer/src/components/compute-enablement-banner.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,39 @@
/**
* Shown on the Workloads page in place of the CLI empty-state when the
* project doesn't (yet) have an Active `compute` ServiceEntitlement — see
* `lib/api.ts`'s `useComputeEntitlement`/`useRequestComputeAccess`. The
* `lib/api.ts`'s `useComputeEntitlement`/`useEnableCompute`. The
* "Enable Compute" button creates the same ServiceEntitlement object that
* `datumctl compute deploy`'s "Would you like to request access?" prompt
* does, so a request made here shows up identically to one made via the CLI.
* `datumctl compute deploy` creates on first use.
*
* The `compact` variant is the same states and wording, sized for the
* project home page's Workloads column.
*/
import { Banner } from './cli-section';
import { ColumnEmpty } from '../cards/home-column-parts';
import { useRequestComputeAccess, type EntitlementPhase } from '../lib/api';
import { useEnableCompute, type EntitlementPhase } from '../lib/api';
import { Button } from '@datum-cloud/datum-ui/button';
import { Icon } from '@datum-cloud/datum-ui/icons';
import { toast } from '@datum-cloud/datum-ui/toast';
import { ClockIcon, ShieldAlertIcon, XCircleIcon } from 'lucide-react';

type BannerState = 'NotRequested' | 'PendingApproval' | 'Rejected';

const COPY: Record<BannerState, { title: string; description: string; icon: React.ReactNode; cta: string }> = {
const COPY: Record<BannerState, { title: string; description: string; icon: React.ReactNode; cta?: string }> = {
NotRequested: {
title: 'This project does not have Compute enabled',
description: 'You need to enable Compute to create Workloads in this project.',
icon: <Icon icon={ShieldAlertIcon} size={32} className="text-primary shrink-0" />,
cta: 'Enable Compute',
},
PendingApproval: {
title: 'Your request is pending',
description:
'Thanks for requesting access to Compute. We will be in touch shortly with a response.',
title: 'Enabling Compute',
description: 'Compute is being enabled for this project. This usually takes a moment.',
icon: <Icon icon={ClockIcon} size={32} className="text-primary shrink-0" />,
cta: 'Enable Compute',
},
Rejected: {
title: 'Compute access was declined',
description: 'The request to enable Compute for this project was declined. You can request access again.',
title: 'Compute could not be enabled',
description: 'Run datumctl compute access request --renew to retry, or contact support.',
icon: <Icon icon={XCircleIcon} size={32} className="text-destructive shrink-0" />,
cta: 'Request access again',
},
};

Expand All @@ -63,18 +59,18 @@ export function ComputeEnablementBanner({
phase: EntitlementPhase | null;
compact?: boolean;
}) {
const { mutate, isPending } = useRequestComputeAccess(projectId);
const { mutate, isPending } = useEnableCompute(projectId);
const state = bannerState(phase);
const copy = COPY[state];

const handleClick = () => {
mutate(undefined, {
onSuccess: () => toast.success('Requested Compute access for this project'),
onError: () => toast.error('Failed to request Compute access'),
onSuccess: () => toast.success('Enabling Compute for this project'),
onError: () => toast.error('Failed to enable Compute'),
});
};

const action = state !== 'PendingApproval' && (
const action = state === 'NotRequested' && (
<Button
loading={isPending}
disabled={isPending}
Expand Down
2 changes: 1 addition & 1 deletion ui/consumer/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ async function requestComputeEntitlement(projectId: string): Promise<void> {
}
}

export function useRequestComputeAccess(
export function useEnableCompute(
projectId: string | undefined
): UseMutationResult<void, ApiError, void> {
const queryClient = useQueryClient();
Expand Down
5 changes: 2 additions & 3 deletions ui/consumer/src/pages/workload-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,8 @@ function MetricCell({
);
}

/** The "Deploy a workload" / "List & inspect workloads" cards — datumctl handles enabling
* Compute itself (the same "Would you like to request access?" prompt the banner's button
* triggers), so these commands work whether or not Compute is enabled yet. */
/** The "Deploy a workload" / "List & inspect workloads" cards — datumctl enables
* Compute on first use, so these commands work whether or not it is enabled yet. */
function WorkloadCliSections({ projectId }: { projectId: string | undefined }) {
return (
<div className="grid grid-cols-1 items-start gap-6 lg:grid-cols-2">
Expand Down
Loading