From 2b6df5f81a2eec92896cd172e81943ac6458e0de Mon Sep 17 00:00:00 2001 From: Xavier Mitault Date: Sun, 31 Mar 2024 22:27:12 +0000 Subject: [PATCH] Change volume file --- src/Instance.ts | 199 ++++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 29 +++++-- src/volumes.ts | 43 +++++++++++ 3 files changed, 265 insertions(+), 6 deletions(-) create mode 100644 src/Instance.ts create mode 100644 src/volumes.ts diff --git a/src/Instance.ts b/src/Instance.ts new file mode 100644 index 0000000..1e41d26 --- /dev/null +++ b/src/Instance.ts @@ -0,0 +1,199 @@ +import * as pulumi from '@pulumi/pulumi'; +import type { ItemType, MachineVolume } from '@aleph-sdk/message'; +import { AuthenticatedAlephHttpClient } from '@aleph-sdk/client'; +import { getAccount, getAlephExplorerUrl } from './utils'; +import type { Volume } from './volumes'; + +export interface InstanceInputs { + channel: pulumi.Input; + authorizedKeys: pulumi.Input; + memory: pulumi.Input; + image: pulumi.Input; + volumes: pulumi.Input>; + storageEngine: pulumi.Input; + accountEnvName: pulumi.Input; +} + +interface InstanceProviderInputs { + channel: string; + authorizedKeys: string[]; + memory: number; + image: string; + volumes: Array; + storageEngine: ItemType.storage | ItemType.ipfs; + accountEnvName: string; +} + +const propChannel = 'channel'; +const propAuthorizedKeys = 'authorizedKeys'; +const propMemory = 'memory'; +const propImage = 'image'; +const propVolumes = 'volumes'; +const propStorageEngine = 'storageEngine'; +const propAccountEnvName = 'accountEnvName'; + +export interface InstanceOutputs { + // inputs + channel: string; + authorizedKeys: string[]; + memory: number; + image: string; + volumes: Array; + storageEngine: ItemType.storage | ItemType.ipfs; + accountEnvName: string; + // outputs + chain: string; + sender: string; + type: string; + item_hash: string; + // Created + aleph_explorer_url: string; +} + +export const getDefaultImage = () => { + return '549ec451d9b099cad112d4aaa2c00ac40fb6729a92ff252ff22eef0b5c3cb613'; +}; + +const InstanceProvider: pulumi.dynamic.ResourceProvider = { + async diff(id: string, olds: InstanceOutputs, news: InstanceProviderInputs) { + const replaces = []; + + if (olds[propChannel] !== news[propChannel]) { + replaces.push(propChannel); + } + if (olds[propAuthorizedKeys] !== news[propAuthorizedKeys]) { + replaces.push(propAuthorizedKeys); + } + if (olds[propMemory] !== news[propMemory]) { + replaces.push(propMemory); + } + if (olds[propImage] !== news[propImage]) { + replaces.push(propImage); + } + if ( + JSON.stringify(olds[propVolumes]) !== JSON.stringify(news[propVolumes]) + ) { + replaces.push(propVolumes); + } + if (olds[propStorageEngine] !== news[propStorageEngine]) { + replaces.push(propStorageEngine); + } + if (replaces.length === 0) { + return { changes: false }; + } + return { replaces: replaces }; + }, + + async update(id: string, olds: InstanceOutputs, news: InstanceInputs) { + throw new Error('Update not implemented; Only Delete and Create'); + }, + + async delete(id: string, props: InstanceOutputs) { + const account = await getAccount(props[propAccountEnvName]); + const client = new AuthenticatedAlephHttpClient(account); + await client.forget({ + channel: props[propChannel], + hashes: [props.item_hash], + sync: true, + }); + }, + + async create( + inputs: InstanceProviderInputs + ): Promise> { + const account = await getAccount(inputs[propAccountEnvName]); + const client = new AuthenticatedAlephHttpClient(account); + const res = await client.createInstance({ + channel: inputs[propChannel], + authorized_keys: inputs[propAuthorizedKeys], + resources: { + memory: inputs[propMemory], + }, + image: inputs[propImage], + volumes: inputs[propVolumes].map((volume): MachineVolume => { + if (volume._type === 'immutable') { + return { + ...volume, + is_read_only: () => true, + }; + } else if (volume._type === 'ephemeral') { + return { + ...volume, + is_read_only: () => false, + }; + } else { + throw new Error('Invalid volume type: This should never happen.'); + } + }), + storageEngine: inputs[propStorageEngine], + }); + const out: InstanceOutputs = { + // inputs + channel: inputs[propChannel], + authorizedKeys: inputs[propAuthorizedKeys], + memory: inputs[propMemory], + image: inputs[propImage], + volumes: inputs[propVolumes], + storageEngine: inputs[propStorageEngine], + accountEnvName: inputs[propAccountEnvName], + // outputs + chain: res.chain, + sender: res.sender, + type: `${res.type}`, + item_hash: res.item_hash, + // Created + aleph_explorer_url: getAlephExplorerUrl( + res.chain, + res.sender, + 'INSTANCE', + res.item_hash + ), + }; + return { + id: `${account.address}-${res.item_hash}`, + outs: out, + }; + }, +}; + +export class Instance extends pulumi.dynamic.Resource { + // inputs + public readonly channel!: pulumi.Output; + public readonly authorizedKeys!: pulumi.Output; + public readonly memory!: pulumi.Output; + public readonly image!: pulumi.Output; + public readonly volumes!: pulumi.Output>; + public readonly storageEngine!: pulumi.Output< + ItemType.storage | ItemType.ipfs + >; + public readonly accountEnvName!: pulumi.Output; + // outputs + public readonly chain!: pulumi.Output; + public readonly sender!: pulumi.Output; + public readonly type!: pulumi.Output; + public readonly item_hash!: pulumi.Output; + // Created + public readonly aleph_explorer_url!: pulumi.Output; + + constructor( + name: string, + args: InstanceInputs, + opts?: pulumi.CustomResourceOptions + ) { + super( + InstanceProvider, + name, + { + // outputs + chain: undefined, + sender: undefined, + type: undefined, + item_hash: undefined, + // Created + aleph_explorer_url: undefined, + ...args, + }, + opts + ); + } +} diff --git a/src/index.ts b/src/index.ts index b15cc21..6c88a5f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,9 +33,6 @@ export { hashData, getAccount, zipPath, getRawFileUrl }; import { // TODO: Subscription (but broken for now) // Subscription, - getImmutableVolume, - getEphemeralVolume, - Volume, ProgramInputs, ProgramOutputs, getDefaultRuntime, @@ -44,11 +41,31 @@ import { export { // TODO: Subscription (but broken for now) // Subscription, - getImmutableVolume, - getEphemeralVolume, - Volume, ProgramInputs, ProgramOutputs, getDefaultRuntime, Program, }; + +import { + getImmutableVolume, + getEphemeralVolume, + Volume, + EphemeralVolume, + ImmutableVolume, +} from './volumes'; +export { + getImmutableVolume, + getEphemeralVolume, + Volume, + EphemeralVolume, + ImmutableVolume, +}; + +import { + InstanceInputs, + InstanceOutputs, + getDefaultImage, + Instance, +} from './Instance'; +export { InstanceInputs, InstanceOutputs, getDefaultImage, Instance }; diff --git a/src/volumes.ts b/src/volumes.ts new file mode 100644 index 0000000..d194265 --- /dev/null +++ b/src/volumes.ts @@ -0,0 +1,43 @@ +type AbstractVolume = { + mount: Array; + _type: 'immutable' | 'ephemeral'; +}; + +export type ImmutableVolume = AbstractVolume & { + ref: string; + use_latest: boolean; + _type: 'immutable'; +}; + +export const getImmutableVolume = ( + ref: string, + use_latest: boolean, + mount: Array +): ImmutableVolume => { + return { + mount: mount, + ref: ref, + use_latest: use_latest, + _type: 'immutable', + }; +}; + +export type EphemeralVolume = AbstractVolume & { + ephemeral: true; + size_mib: number; + _type: 'ephemeral'; +}; + +export const getEphemeralVolume = ( + size_mib: number, + mount: Array +): EphemeralVolume => { + return { + mount: mount, + ephemeral: true, + size_mib: size_mib, + _type: 'ephemeral', + }; +}; + +export type Volume = ImmutableVolume | EphemeralVolume;