From 59c45c6160ec7850e0ea85197667011f71126192 Mon Sep 17 00:00:00 2001 From: Chris Rybicki Date: Wed, 31 Jul 2024 20:34:25 -0400 Subject: [PATCH] split into multiple files --- cloudv2/counter-aws.w | 97 ++++++++++++ cloudv2/counter-sim.w | 92 +++++++++++ cloudv2/counter-types.w | 28 ++++ cloudv2/counter.w | 199 +----------------------- cloudv2/test/counter/aws-counter.test.w | 53 ++++--- 5 files changed, 251 insertions(+), 218 deletions(-) create mode 100644 cloudv2/counter-aws.w create mode 100644 cloudv2/counter-sim.w create mode 100644 cloudv2/counter-types.w diff --git a/cloudv2/counter-aws.w b/cloudv2/counter-aws.w new file mode 100644 index 00000000..afe92ec0 --- /dev/null +++ b/cloudv2/counter-aws.w @@ -0,0 +1,97 @@ +bring aws; + +bring "./counter-types.w" as counter_types; +bring "@cdktf/provider-aws" as tfaws; +bring "./util.w" as myutil; + +pub interface IAwsCounter { + dynamoTableArn(): str; // TODO: support properties on interfaces - https://github.com/winglang/wing/issues/4961 + dynamoTableName(): str; +} + +pub class Counter_tfaws impl counter_types.ICounter, IAwsCounter { + initial: num; + table: tfaws.dynamodbTable.DynamodbTable; + hashKey: str; + defaultKey: str; // TODO: module-level constants - https://github.com/winglang/wing/issues/3606 + envKey: str; + tableName: str; + tableArn: str; + new(props: counter_types.CounterProps) { + this.initial = props.initial ?? 0; + this.hashKey = "id"; + this.table = new tfaws.dynamodbTable.DynamodbTable( + name: myutil.friendlyName(this), + attribute: [{ name: this.hashKey, type: "S" }], + hashKey: this.hashKey, + billingMode: "PAY_PER_REQUEST", + ) as "Default"; + this.defaultKey = "default"; + this.envKey = "COUNTER_" + myutil.shortHash(this); + this.tableName = this.table.name; + this.tableArn = this.table.arn; + } + + extern "./counter-aws.ts" static inflight _inc(amount: num, key: str, tableName: str, hashKey: str, initial: num): num; + extern "./counter-aws.ts" static inflight _dec(amount: num, key: str, tableName: str, hashKey: str, initial: num): num; + extern "./counter-aws.ts" static inflight _peek(key: str, tableName: str, hashKey: str, initial: num): num; + extern "./counter-aws.ts" static inflight _set(value: num, key: str, tableName: str, hashKey: str): void; + + pub inflight inc(amount: num?, key: str?): num { + return Counter_tfaws._inc(amount ?? 1, key ?? this.defaultKey, this.tableName, this.hashKey, this.initial); + } + + pub inflight dec(amount: num?, key: str?): num { + return Counter_tfaws._dec(amount ?? 1, key ?? this.defaultKey, this.tableName, this.hashKey, this.initial); + } + + pub inflight peek(key: str?): num { + return Counter_tfaws._peek(key ?? this.defaultKey, this.tableName, this.hashKey, this.initial); + } + + pub inflight set(value: num, key: str?): void { + Counter_tfaws._set(value, key ?? this.defaultKey, this.tableName, this.hashKey); + } + + pub onLift(host: std.IInflightHost, ops: Array) { + // TODO: implement aws.Host.from + // if aws.Host.from(host) == nil { + // throw "Counter for \"tf-aws\" can only be lifted by a class implementing aws.IHost"; + // } + + // let awsHost: aws.IAwsInflightHost = aws.Host.from(host)!; + let awsHost: aws.IAwsInflightHost = unsafeCast(host); + let actions = MutArray[]; + if ops.contains("inc") || ops.contains("dec") || ops.contains("set") { + actions.push("dynamodb:UpdateItem"); + } + if ops.contains("peek") { + actions.push("dynamodb:GetItem"); + } + + awsHost.addPolicyStatements({ + actions: actions.copy(), + effect: aws.Effect.ALLOW, + resources: [this.tableArn], + }); + awsHost.addEnvironment(this.envKey, this.tableName); + } + + pub dynamoTableArn(): str { + return this.tableArn; + } + + pub dynamoTableName(): str { + return this.tableName; + } +} + +pub class AwsCounter { + pub static from(c: counter_types.ICounter): IAwsCounter? { + let obj = unsafeCast(c); + if obj?.dynamoTableArn != nil && obj?.dynamoTableName != nil { + return obj; + } + return nil; + } +} diff --git a/cloudv2/counter-sim.w b/cloudv2/counter-sim.w new file mode 100644 index 00000000..6a7d3478 --- /dev/null +++ b/cloudv2/counter-sim.w @@ -0,0 +1,92 @@ +bring fs; +bring sim; + +bring "./counter-types.w" as counter_types; + +inflight class CounterBackend impl sim.IResource { + valuesFile: str; + initial: num; + statedir: str; + values: MutMap; + new(ctx: sim.IResourceContext, initial: num) { + this.initial = initial; + + this.statedir = ctx.statedir(); + this.valuesFile = fs.join(this.statedir, "values.json"); + if fs.exists(this.valuesFile) { + let data = fs.readJson(this.valuesFile); + // TODO: MutMap.fromJson(...) - https://github.com/winglang/wing/issues/1796 + this.values = unsafeCast(data["values"]); + } else { + this.values = {}; + } + } + + pub onStop() { + fs.writeJson(this.valuesFile, { "values": this.values.copy() }); + } + + pub inc(amount: num, key: str): num { + let prev = this.values.tryGet(key) ?? this.initial; + this.values[key] = prev + amount; + return prev; + } + + pub dec(amount: num, key: str): num { + let prev = this.values.tryGet(key) ?? this.initial; + this.values[key] = prev - amount; + return prev; + } + + pub peek(key: str): num { + return this.values.tryGet(key) ?? this.initial; + } + + pub set(value: num, key: str) { + this.values[key] = value; + } +} + +// TODO: internal access modifiers - https://github.com/winglang/wing/issues/4156 + +pub class Counter_sim impl counter_types.ICounter { + initial: num; + backend: sim.Resource; + defaultKey: str; // TODO: module-level constants - https://github.com/winglang/wing/issues/3606 + new(props: counter_types.CounterProps) { + this.initial = props.initial ?? 0; + this.backend = new sim.Resource(inflight (ctx) => { + return new CounterBackend(ctx, this.initial); + }) as "Backend"; + nodeof(this.backend).icon = "calculator"; + nodeof(this.backend).color = "lime"; + this.defaultKey = "default"; + } + + pub inflight inc(amount: num?, key: str?): num { + let response = this.backend.call("inc", Json [amount ?? 1, key ?? this.defaultKey]); + return num.fromJson(response); + } + + pub inflight dec(amount: num?, key: str?): num { + let response = this.backend.call("dec", Json [amount ?? 1, key ?? this.defaultKey]); + return num.fromJson(response); + } + + pub inflight peek(key: str?): num { + let response = this.backend.call("peek", Json [key ?? this.defaultKey]); + return num.fromJson(response); + } + + pub inflight set(value: num, key: str?): void { + this.backend.call("set", Json [value, key ?? this.defaultKey]); + } + + // TODO: rename this to std.IHost + pub onLift(host: std.IInflightHost, ops: Array) { + // TODO: check that host is sim.ISimHost + // if sim.Host.from(host) == nil { + // throw "Counter_sim can only be lifted by an ISimHost"; + // } + } +} diff --git a/cloudv2/counter-types.w b/cloudv2/counter-types.w new file mode 100644 index 00000000..5712b101 --- /dev/null +++ b/cloudv2/counter-types.w @@ -0,0 +1,28 @@ +// TODO: Default values for struct fields - https://github.com/winglang/wing/issues/3121 + +pub struct CounterProps { + /// The initial value of the counter + /// @default 0 + initial: num?; +} + +pub interface ICounter { + /// Increments the counter atomically by a certain amount and returns the previous value. + /// - `amount` The amount to increment by (defaults to 1) + /// - `key` The key of the counter (defaults to "default") + inflight inc(amount: num?, key: str?): num; + + /// Decrements the counter atomically by a certain amount and returns the previous value. + /// - `amount` The amount to decrement by (defaults to 1) + /// - `key` The key of the counter (defaults to "default") + inflight dec(amount: num?, key: str?): num; + + /// Returns the current value of the counter. + /// - `key` The key of the counter (defaults to "default") + inflight peek(key: str?): num; + + /// Sets the value of the counter. + /// - `value` The new value of the counter + /// - `key` The key of the counter (defaults to "default") + inflight set(value: num, key: str?): void; +} diff --git a/cloudv2/counter.w b/cloudv2/counter.w index 2ca0bc51..ef21803a 100644 --- a/cloudv2/counter.w +++ b/cloudv2/counter.w @@ -5,42 +5,14 @@ bring ui; bring util; bring sim; -bring "@cdktf/provider-aws" as tfaws; -bring "./util.w" as myutil; +bring "./counter-aws.w" as counter_aws; +bring "./counter-sim.w" as counter_sim; +bring "./counter-types.w" as counter_types; -// TODO: Default values for struct fields - https://github.com/winglang/wing/issues/3121 - -pub struct CounterProps { - /// The initial value of the counter - /// @default 0 - initial: num?; -} - -interface ICounter { - /// Increments the counter atomically by a certain amount and returns the previous value. - /// - `amount` The amount to increment by (defaults to 1) - /// - `key` The key of the counter (defaults to "default") - inflight inc(amount: num?, key: str?): num; - - /// Decrements the counter atomically by a certain amount and returns the previous value. - /// - `amount` The amount to decrement by (defaults to 1) - /// - `key` The key of the counter (defaults to "default") - inflight dec(amount: num?, key: str?): num; - - /// Returns the current value of the counter. - /// - `key` The key of the counter (defaults to "default") - inflight peek(key: str?): num; - - /// Sets the value of the counter. - /// - `value` The new value of the counter - /// - `key` The key of the counter (defaults to "default") - inflight set(value: num, key: str?): void; -} - -pub class Counter impl ICounter { - inner: ICounter; +pub class Counter impl counter_types.ICounter { + inner: counter_types.ICounter; pub initial: num; - new(props: CounterProps) { + new(props: counter_types.CounterProps) { nodeof(this).title = "Counter"; nodeof(this).description = "A distributed atomic counter"; nodeof(this).icon = "calculator"; @@ -50,9 +22,9 @@ pub class Counter impl ICounter { let id = nodeof(this).id; this.initial = props.initial ?? 0; if target == "sim" { - this.inner = new Counter_sim(props) as id; + this.inner = new counter_sim.Counter_sim(props) as id; } elif target == "tf-aws" { - this.inner = new Counter_tfaws(props) as id; + this.inner = new counter_aws.Counter_tfaws(props) as id; } else { throw "Unsupported target: " + target; } @@ -84,158 +56,3 @@ pub class Counter impl ICounter { this.inner.set(value, key); } } - -inflight class CounterBackend impl sim.IResource { - valuesFile: str; - initial: num; - statedir: str; - values: MutMap; - new(ctx: sim.IResourceContext, initial: num) { - this.initial = initial; - - this.statedir = ctx.statedir(); - this.valuesFile = fs.join(this.statedir, "values.json"); - if fs.exists(this.valuesFile) { - let data = fs.readJson(this.valuesFile); - // TODO: MutMap.fromJson(...) - https://github.com/winglang/wing/issues/1796 - this.values = unsafeCast(data["values"]); - } else { - this.values = {}; - } - } - - pub onStop() { - fs.writeJson(this.valuesFile, { "values": this.values.copy() }); - } - - pub inc(amount: num, key: str): num { - let prev = this.values.tryGet(key) ?? this.initial; - this.values[key] = prev + amount; - return prev; - } - - pub dec(amount: num, key: str): num { - let prev = this.values.tryGet(key) ?? this.initial; - this.values[key] = prev - amount; - return prev; - } - - pub peek(key: str): num { - return this.values.tryGet(key) ?? this.initial; - } - - pub set(value: num, key: str) { - this.values[key] = value; - } -} - -class Counter_sim impl ICounter { - initial: num; - backend: sim.Resource; - defaultKey: str; // TODO: module-level constants - https://github.com/winglang/wing/issues/3606 - new(props: CounterProps) { - this.initial = props.initial ?? 0; - this.backend = new sim.Resource(inflight (ctx) => { - return new CounterBackend(ctx, this.initial); - }) as "Backend"; - nodeof(this.backend).icon = "calculator"; - nodeof(this.backend).color = "lime"; - this.defaultKey = "default"; - } - - pub inflight inc(amount: num?, key: str?): num { - let response = this.backend.call("inc", Json [amount ?? 1, key ?? this.defaultKey]); - return num.fromJson(response); - } - - pub inflight dec(amount: num?, key: str?): num { - let response = this.backend.call("dec", Json [amount ?? 1, key ?? this.defaultKey]); - return num.fromJson(response); - } - - pub inflight peek(key: str?): num { - let response = this.backend.call("peek", Json [key ?? this.defaultKey]); - return num.fromJson(response); - } - - pub inflight set(value: num, key: str?): void { - this.backend.call("set", Json [value, key ?? this.defaultKey]); - } - - // TODO: rename this to std.IHost - pub onLift(host: std.IInflightHost, ops: Array) { - // TODO: check that host is sim.ISimHost - // if sim.Host.from(host) == nil { - // throw "Counter_sim can only be lifted by an ISimHost"; - // } - } -} - -class Counter_tfaws impl ICounter { - initial: num; - table: tfaws.dynamodbTable.DynamodbTable; - hashKey: str; - defaultKey: str; // TODO: module-level constants - https://github.com/winglang/wing/issues/3606 - envKey: str; - tableName: str; - tableArn: str; - new(props: CounterProps) { - this.initial = props.initial ?? 0; - this.hashKey = "id"; - this.table = new tfaws.dynamodbTable.DynamodbTable( - name: myutil.friendlyName(this), - attribute: [{ name: this.hashKey, type: "S" }], - hashKey: this.hashKey, - billingMode: "PAY_PER_REQUEST", - ) as "Default"; - this.defaultKey = "default"; - this.envKey = "COUNTER_" + myutil.shortHash(this); - this.tableName = this.table.name; - this.tableArn = this.table.arn; - } - - extern "./counter-aws.ts" static inflight _inc(amount: num, key: str, tableName: str, hashKey: str, initial: num): num; - extern "./counter-aws.ts" static inflight _dec(amount: num, key: str, tableName: str, hashKey: str, initial: num): num; - extern "./counter-aws.ts" static inflight _peek(key: str, tableName: str, hashKey: str, initial: num): num; - extern "./counter-aws.ts" static inflight _set(value: num, key: str, tableName: str, hashKey: str): void; - - pub inflight inc(amount: num?, key: str?): num { - return Counter_tfaws._inc(amount ?? 1, key ?? this.defaultKey, this.tableName, this.hashKey, this.initial); - } - - pub inflight dec(amount: num?, key: str?): num { - return Counter_tfaws._dec(amount ?? 1, key ?? this.defaultKey, this.tableName, this.hashKey, this.initial); - } - - pub inflight peek(key: str?): num { - return Counter_tfaws._peek(key ?? this.defaultKey, this.tableName, this.hashKey, this.initial); - } - - pub inflight set(value: num, key: str?): void { - Counter_tfaws._set(value, key ?? this.defaultKey, this.tableName, this.hashKey); - } - - pub onLift(host: std.IInflightHost, ops: Array) { - // TODO: implement aws.Host.from - // if aws.Host.from(host) == nil { - // throw "Counter for \"tf-aws\" can only be lifted by a class implementing aws.IHost"; - // } - - // let awsHost: aws.IAwsInflightHost = aws.Host.from(host)!; - let awsHost: aws.IAwsInflightHost = unsafeCast(host); - let actions = MutArray[]; - if ops.contains("inc") || ops.contains("dec") || ops.contains("set") { - actions.push("dynamodb:UpdateItem"); - } - if ops.contains("peek") { - actions.push("dynamodb:GetItem"); - } - - awsHost.addPolicyStatements({ - actions: actions.copy(), - effect: aws.Effect.ALLOW, - resources: [this.tableArn], - }); - awsHost.addEnvironment(this.envKey, this.tableName); - } -} diff --git a/cloudv2/test/counter/aws-counter.test.w b/cloudv2/test/counter/aws-counter.test.w index acae8bf1..67d3f398 100644 --- a/cloudv2/test/counter/aws-counter.test.w +++ b/cloudv2/test/counter/aws-counter.test.w @@ -1,5 +1,4 @@ bring "../../" as cloud; -bring aws; bring util; let target = util.env("WING_TARGET"); @@ -8,31 +7,31 @@ let counter = new cloud.Counter(initial: 1) as "aws-wing-counter"; // TODO: implement aws.Counter.from -// let getCounterInfo = (c: cloud.Counter): Map? => { -// if let counter = aws.Counter.from(c) { -// return { -// dynamoTableArn: counter.dynamoTableArn, -// dynamoTableName: counter.dynamoTableName, -// }; -// } -// return nil; -// }; +let getCounterInfo = (c: cloud.Counter): Map? => { + if let counter = cloud.AwsCounter.from(c) { + return { + dynamoTableArn: counter.dynamoTableArn(), + dynamoTableName: counter.dynamoTableName(), + }; + } + return nil; +}; -// let counterInfo = getCounterInfo(counter); +let counterInfo = getCounterInfo(counter); -// test "validates the AWS counter name" { -// if let counter = counterInfo { -// if target == "tf-aws" { -// assert(counter.get("dynamoTableArn").contains("arn:aws:dynamodb:")); -// assert(counter.get("dynamoTableArn").contains("aws-wing-counter")); -// assert(counter.get("dynamoTableName").contains("aws-wing-counter")); -// } else { -// assert(counter.get("dynamoTableArn").contains("arn:aws:dynamodb:")); -// assert(counter.get("dynamoTableArn").contains("awswingcounter")); -// assert(counter.get("dynamoTableName").contains("awswingcounter")); -// } -// } else { -// // If the test is not on AWS, it should not fail, so I am returning true. -// assert(true); -// } -// } +test "validates the AWS counter name" { + if let counter = counterInfo { + if target == "tf-aws" { + assert(counter.get("dynamoTableArn").contains("arn:aws:dynamodb:")); + assert(counter.get("dynamoTableArn").contains("aws-wing-counter")); + assert(counter.get("dynamoTableName").contains("aws-wing-counter")); + } else { + assert(counter.get("dynamoTableArn").contains("arn:aws:dynamodb:")); + assert(counter.get("dynamoTableArn").contains("awswingcounter")); + assert(counter.get("dynamoTableName").contains("awswingcounter")); + } + } else { + // If the test is not on AWS, it should not fail, so I am returning true. + assert(true); + } +}