Skip to content

Commit

Permalink
Fixed issues with environment confirmation during datacenter applies
Browse files Browse the repository at this point in the history
  • Loading branch information
davidthor committed Dec 6, 2023
1 parent 325da65 commit 41ecac5
Show file tree
Hide file tree
Showing 16 changed files with 586 additions and 128 deletions.
284 changes: 283 additions & 1 deletion deno.lock

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions examples/datacenters/local/datacenter.arc
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,27 @@ environment {
image = module.build.image
}
}

volume {
module "volume" {
build = "./volume"

environment = {
DOCKER_HOST = "unix:///var/run/docker.sock"
}

volume {
host_path = "/var/run/docker.sock"
mount_path = "/var/run/docker.sock"
}

inputs = {
name = "${node.component}-${node.name}"
}
}

outputs = {
id = module.volume.id
}
}
}
19 changes: 11 additions & 8 deletions examples/datacenters/local/deployment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const config = new pulumi.Config();
export const name = config.require('name');

type Config = {
name?: string;
name: string;
image: string;
command?: string[];
entrypoint?: string[];
Expand Down Expand Up @@ -90,23 +90,26 @@ for (const key in inputServices) {
const inputIngresses = config.getObject<Config['ingresses']>('ingresses') || [];
for (const key in inputIngresses) {
const value = inputIngresses[key];
const routerKey = value.subdomain.replace(/\./g, '-').replace(/\*/g, 'star');
if (value.protocol === 'http') {
labels.push({
label: `traefik.http.routers.${value.subdomain}.rule`,
value: `Host(\`${value.host}\`) && PathPrefix(\`${value.path || '/'}\`)`,
label: `traefik.http.routers.${routerKey}.rule`,
value: value.host.includes('*')
? `HostRegexp(\`${value.host.replace('*', '{subdomain:[a-z_-]+}')}\`) && PathPrefix(\`${value.path || '/'}\`)`
: `Host(\`${value.host}\`) && PathPrefix(\`${value.path || '/'}\`)`,
}, {
label: `traefik.http.routers.${value.subdomain}.service`,
label: `traefik.http.routers.${routerKey}.service`,
value: value.service,
});
} else {
labels.push({
label: `traefik.tcp.routers.${value.subdomain}.rule`,
label: `traefik.tcp.routers.${routerKey}.rule`,
value: `HostSNI(\`${value.host}\`)`
}, {
label: `traefik.tcp.routers.${value.subdomain}.service`,
label: `traefik.tcp.routers.${routerKey}.service`,
value: value.service,
}, {
label: `traefik.tcp.routers.${value.subdomain}.tls.passthrough`,
label: `traefik.tcp.routers.${routerKey}.tls.passthrough`,
value: 'true',
});
}
Expand All @@ -129,4 +132,4 @@ const deployment = new docker.Container("deployment", {
})),
});

export const id = deployment.id.apply(id => id.toString());
export const id = deployment.id.apply(id => id.toString());
16 changes: 0 additions & 16 deletions examples/datacenters/local/network/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,6 @@ import * as pulumi from "@pulumi/pulumi";

let config = new pulumi.Config();

type Config = {
name?: string;
image: string;
command?: string[];
labels?: Record<string, string>;
services?: Record<string, {
hostname: string;
port: number;
protocol: string;
}>;
ports?: {
internal: number;
external?: number;
}[];
};

const network = new docker.Network('network', {
name: config.get('name'),
});
Expand Down
14 changes: 13 additions & 1 deletion src/@resources/volume/outputs.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,19 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"VolumeOutputs": {
"additionalProperties": {},
"additionalProperties": false,
"properties": {
"id": {
"description": "The unique ID of the volume",
"examples": [
"my-volume"
],
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
}
}
Expand Down
9 changes: 8 additions & 1 deletion src/@resources/volume/outputs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
export type VolumeOutputs = Record<string, unknown>;
export type VolumeOutputs = {
/**
* The unique ID of the volume
*
* @example "my-volume"
*/
id: string;
};

export default VolumeOutputs;
9 changes: 7 additions & 2 deletions src/commands/apply/datacenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,20 @@ async function apply_datacenter_action(options: ApplyDatacenterOptions, name: st
if (datacenterEnvironments.length > 0) {
for (const environmentRecord of datacenterEnvironments) {
console.log(`Updating environment ${environmentRecord.name}`);
await applyEnvironment({
const { success } = await applyEnvironment({
command_helper,
name: environmentRecord.name,
logger,
autoApprove: true,
targetEnvironment: environmentRecord.config,
});

if (success) {
console.log(`Environment ${environmentRecord.name} updated successfully`);
} else {
console.error(`%cEnvironment ${environmentRecord.name} update failed`, 'color: red');
}
}
console.log('Environments updated successfully');
command_helper.infraRenderer.doneRenderingGraph();
}
}).catch(async (err) => {
Expand Down
8 changes: 7 additions & 1 deletion src/commands/apply/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,20 @@ export async function applyEnvironmentAction(options: ApplyEnvironmentOptions, n
});
}

return applyEnvironment({
const { success, update } = await applyEnvironment({
command_helper,
logger,
name,
autoApprove: options.autoApprove,
datacenter: options.datacenter,
targetEnvironment: await parseEnvironment(config_path || {}),
});

if (!success) {
console.log(`Environment ${update ? 'update' : 'creation'} failed`);
} else {
console.log(`Environment ${name} ${update ? 'updated' : 'created'} successfully`);
}
}

export default ApplyEnvironmentCommand;
73 changes: 29 additions & 44 deletions src/commands/apply/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,69 +45,55 @@ export const promptForDatacenter = async (command_helper: CommandHelper, name?:
};

export const applyEnvironment = async (options: ApplyEnvironmentOptions) => {
const environmentRecord = await options.command_helper.environmentStore.get(options.name);
const notHasDatacenter = !options.datacenter && !environmentRecord;

let datacenterRecord: DatacenterRecord | undefined;
if (notHasDatacenter) {
datacenterRecord = await ArcctlConfig.getDefaultDatacenter(options.command_helper);
const existingEnvironmentRecord = await options.command_helper.environmentStore.get(options.name);
let datacenterRecord = options.datacenter
? await options.command_helper.datacenterStore.get(options.datacenter)
: existingEnvironmentRecord
? await options.command_helper.datacenterStore.get(existingEnvironmentRecord.datacenter)
: await ArcctlConfig.getDefaultDatacenter(options.command_helper);

if (!datacenterRecord) {
datacenterRecord = await promptForDatacenter(options.command_helper);
}

const targetDatacenterName = !datacenterRecord
? (await promptForDatacenter(options.command_helper, options.datacenter)).name
: datacenterRecord.name || environmentRecord?.datacenter;

const targetDatacenter = targetDatacenterName
? await options.command_helper.datacenterStore.get(targetDatacenterName)
: undefined;
if (!targetDatacenter) {
console.error(`Couldn't find a datacenter named ${targetDatacenterName}`);
Deno.exit(1);
if (!datacenterRecord) {
throw new Error(`No valid datacenter provided`);
}

const targetEnvironment = options.targetEnvironment || await parseEnvironment({});

const environmentGraph = await targetEnvironment.getGraph(
const targetAppGraph = await targetEnvironment.getGraph(
options.name,
options.command_helper.componentStore,
options.debug,
);

const targetGraph = targetDatacenter.config.getGraph(environmentGraph, {
const targetInfraGraph = datacenterRecord.config.getGraph(targetAppGraph, {
environmentName: options.name,
datacenterName: targetDatacenter.name,
datacenterName: datacenterRecord.name,
});
targetGraph.validate();
targetInfraGraph.validate();

const startingDatacenter = (await options.command_helper.datacenterStore.get(targetDatacenterName!))!;
startingDatacenter.config.getGraph(environmentGraph, {
environmentName: options.name,
datacenterName: targetDatacenter.name,
});

const startingGraph = environmentRecord ? environmentRecord.priorState : targetDatacenter.priorState;

const infraGraph = await InfraGraph.plan({
before: startingGraph,
after: targetGraph,
const plannedChanges = await InfraGraph.plan({
before: existingEnvironmentRecord ? existingEnvironmentRecord.priorState : datacenterRecord.priorState,
after: targetInfraGraph,
context: PlanContext.Environment,
});

infraGraph.validate();
await options.command_helper.infraRenderer.confirmGraph(infraGraph, options.autoApprove);
plannedChanges.validate();
await options.command_helper.infraRenderer.confirmGraph(plannedChanges, options.autoApprove);

let interval: number | undefined = undefined;
if (!options.logger) {
interval = setInterval(() => {
options.command_helper.infraRenderer.renderGraph(infraGraph, { clear: true });
options.command_helper.infraRenderer.renderGraph(plannedChanges, { clear: true });
}, 1000 / cliSpinners.dots.frames.length);
}

const success = await options.command_helper.environmentUtils.applyEnvironment(
options.name,
startingDatacenter,
targetEnvironment!,
infraGraph,
datacenterRecord,
targetEnvironment,
plannedChanges,
{
logger: options.logger,
},
Expand All @@ -116,12 +102,11 @@ export const applyEnvironment = async (options: ApplyEnvironmentOptions) => {
if (interval) {
clearInterval(interval);
}
options.command_helper.infraRenderer.renderGraph(infraGraph, { clear: !options.logger, disableSpinner: true });
options.command_helper.infraRenderer.renderGraph(plannedChanges, { clear: !options.logger, disableSpinner: true });
options.command_helper.infraRenderer.doneRenderingGraph();

if (!success) {
console.log(`Environment ${environmentRecord ? 'update' : 'creation'} failed`);
} else {
console.log(`Environment ${options.name} ${environmentRecord ? 'updated' : 'created'} successfully`);
}
return {
success,
update: !!existingEnvironmentRecord,
};
};
9 changes: 1 addition & 8 deletions src/commands/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,10 @@ async function tag_action(options: GlobalOptions, source: string, target: string

component.tag(async (sourceRef: string, targetName: string) => {
const imageRepository = new ImageRepository(target);
const targetRef = imageRepository.toString() + '-deployments-' + targetName;
const targetRef = imageRepository.toString() + '-' + targetName;
await exec('docker', { args: ['tag', sourceRef, targetRef] });
console.log(`Deployment Tagged: ${targetRef}`);
return targetRef;
}, async (sourceRef: string, deploymentName: string, volumeName: string) => {
const imageRepository = new ImageRepository(target);
const targetRef = imageRepository.toString() + '-deployments-' + deploymentName + '-volumes-' + volumeName;

await exec('docker', { args: ['tag', sourceRef, targetRef] });
console.log(`Volume Tagged: ${targetRef}`);
return targetRef;
});

command_helper.componentStore.tag(source, target);
Expand Down
16 changes: 2 additions & 14 deletions src/components/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ export type GraphContext = {
};
};

export type VolumeBuildFn = (options: {
deployment_name: string;
volume_name: string;
host_path: string;
}) => Promise<string>;

export type DockerBuildFn = (options: {
name: string;
context: string;
Expand All @@ -28,12 +22,6 @@ export type DockerTagFn = (
targetName: string,
) => Promise<string>;

export type VolumeTagFn = (
digest: string,
deploymentName: string,
volumeName: string,
) => Promise<string>;

export type DockerPushFn = (image: string) => Promise<void>;

export type ComponentDependencies = Array<{
Expand All @@ -46,9 +34,9 @@ export abstract class Component {

public abstract getGraph(context: GraphContext): AppGraph;

public abstract build(buildFn: DockerBuildFn, volumeFn: VolumeBuildFn): Promise<Component>;
public abstract build(buildFn: DockerBuildFn): Promise<Component>;

public abstract tag(tagFn: DockerTagFn, volumeTagFn: VolumeTagFn): Promise<Component>;
public abstract tag(tagFn: DockerTagFn): Promise<Component>;

public abstract push(pushFn: DockerPushFn): Promise<Component>;
}
Loading

0 comments on commit 41ecac5

Please sign in to comment.