Dot Net User Group

Many many
other brilliant speakers

Many many
other brilliant speakers

Many many
other brilliant speakers

Many many
other brilliant speakers

Whats Dot Net?

Oh what is this?
A calendar

Ah yes... Thursdays... the normal day for the Birmingham... dot net...

WHICH ON WEDNESDAYS RYAN!!

Who Am I?

  • Hi I'm Ryan.
  • I'm a community focused software Engineer currently working @ Bluetel
  • I help Co-organise codebar Birmingham
  • I have an unhealthy obsession with programming mascots
  • Probably owns a calendar

On a complete tangent

Agenda

  • A look into IAC as a concept (~5 minutes)
  • A introduction to CDK (15 minutes)
  • Live demo... what could go wrong?
  • A introduction to pullumi (10 minutes)
  • Live Demo 2... twice is the charm
  • Comparisons (5 minutes)
  • Ze End

What is IAC

The simulation
We live in

The simulation
We live in

The simulation
We live in

Our AI overlords

This concept broadly known as Infrastructure As Code

Why bother?
¯\_(ツ)_/¯

Why bother?
¯\_(ツ)_/¯

Super Important Business
Critical Server

Super Important Business
Critical Server

Super Important Business
Critical Server

"Restructuring"

Super Important Business
Critical Server

"Restructuring"

Super Important Business
Critical Server

Super Important Business
Critical Server

Years Pass...

Super Important Business
Critical Server

Years Pass...

Super Important Business
Critical Server

Years Pass...

Super Important Business
Critical Server

Years Pass...

Super Important Business
Critical Server

Years Pass...

Super Important Business
Critical Sever

[DO NOT TOUCH]

Super Important Business
Critical Sever

[DO NOT TOUCH]

You

S̵̯͊u̸̠͑p̴̩̓ȇ̴̗r̴͓̈́ ̴̡͐Ḯ̴̮m̷̦̃p̵̩̆o̵̢̾r̵̳͑t̸̨̂á̶̮n̴̤͘t̷̩̏ ̵͕́B̸̰͋ų̵̏s̷̝̓i̴̦̽n̸̬̈ē̴̟s̸̮̒s̴̻̊
̷̤̊C̴̜͝r̷͍̐ĭ̸͈t̵̹͒i̵͎̓ç̸̓ã̴͙ḷ̸͆ ̵̮̊S̷̯͊e̴̠̍v̸̡̒e̶̞͒r̶̲̕™̷̠̓
̸̹͋
̴̡̎[̶̨́D̴̠̈́O̸̭͑ ̵̠̃N̴͈̂Ȍ̵̺T̸͖̓ ̵̮͘T̶̢̅O̷̤͂U̵͓̍C̴̭̍H̴͖͂]̷͔̌

You

S̵̯͊u̸̠͑p̴̩̓ȇ̴̗r̴͓̈́ ̴̡͐Ḯ̴̮m̷̦̃p̵̩̆o̵̢̾r̵̳͑t̸̨̂á̶̮n̴̤͘t̷̩̏ ̵͕́B̸̰͋ų̵̏s̷̝̓i̴̦̽n̸̬̈ē̴̟s̸̮̒s̴̻̊
̷̤̊C̴̜͝r̷͍̐ĭ̸͈t̵̹͒i̵͎̓ç̸̓ã̴͙ḷ̸͆ ̵̮̊S̷̯͊e̴̠̍v̸̡̒e̶̞͒r̶̲̕™̷̠̓
̸̹͋
̴̡̎[̶̨́D̴̠̈́O̸̭͑ ̵̠̃N̴͈̂Ȍ̵̺T̸͖̓ ̵̮͘T̶̢̅O̷̤͂U̵͓̍C̴̭̍H̴͖͂]̷͔̌

You

What If Instead...

Super Important Business
Critical Server

Super Important Business
Critical Server

resource "some_server" {
}

Super Important Business
Critical Server

resource "some_server" {
    # Tried turning it off and on again
    # did not work
    spanner = True
}

Super Important Business
Critical Server

resource "the_kitchen_sink" {
    always_slightly_too_hot = true
}

resource "some_server" {
    # Tried turning it off and on again
    # did not work
    spanner = True
    connected_sink = the_kitchen_sink.id
}

Super Important Business
Critical Server

resource "the_kitchen_sink" {
    always_slightly_too_hot = true
}

resource "some_server" {
    # Tried turning it off and on again
    # did not work
    spanner = True
    connected_sink = the_kitchen_sink.id
    c̷u̸t̴h̷u̶l̷u̴ = True
}

Super Important Business
Critical Server

resource "the_kitchen_sink" {
    always_slightly_too_hot = true
}

resource "some_server" {
    # Tried turning it off and on again
    # did not work
    spanner = True
    connected_sink = the_kitchen_sink.id
    c̷u̸t̴h̷u̶l̷u̴ = True
}

S̵̯͊u̸̠͑p̴̩̓ȇ̴̗r̴͓̈́ ̴̡͐Ḯ̴̮m̷̦̃p̵̩̆o̵̢̾r̵̳͑t̸̨̂á̶̮n̴̤͘t̷̩̏ ̵͕́B̸̰͋ų̵̏s̷̝̓i̴̦̽n̸̬̈ē̴̟s̸̮̒s̴̻̊
̷̤̊C̴̜͝r̷͍̐ĭ̸͈t̵̹͒i̵͎̓ç̸̓ã̴͙ḷ̸͆ ̵̮̊S̷̯͊e̴̠̍v̸̡̒e̶̞͒r̶̲̕™̷̠̓
̸̹͋
̴̡̎[̶̨́D̴̠̈́O̸̭͑ ̵̠̃N̴͈̂Ȍ̵̺T̸͖̓ ̵̮͘T̶̢̅O̷̤͂U̵͓̍C̴̭̍H̴͖͂]̷͔̌

You

S̵̯͊u̸̠͑p̴̩̓ȇ̴̗r̴͓̈́ ̴̡͐Ḯ̴̮m̷̦̃p̵̩̆o̵̢̾r̵̳͑t̸̨̂á̶̮n̴̤͘t̷̩̏ ̵͕́B̸̰͋ų̵̏s̷̝̓i̴̦̽n̸̬̈ē̴̟s̸̮̒s̴̻̊
̷̤̊C̴̜͝r̷͍̐ĭ̸͈t̵̹͒i̵͎̓ç̸̓ã̴͙ḷ̸͆ ̵̮̊S̷̯͊e̴̠̍v̸̡̒e̶̞͒r̶̲̕™̷̠̓
̸̹͋
̴̡̎[̶̨́D̴̠̈́O̸̭͑ ̵̠̃N̴͈̂Ȍ̵̺T̸͖̓ ̵̮͘T̶̢̅O̷̤͂U̵͓̍C̴̭̍H̴͖͂]̷͔̌

You

New Super Duper
Critical Server

Destroy

Recreate

You

New Super Duper
Critical Server

What are our options when it comes to C#?

Ehhh....

https://landscape.cncf.io

Choose Your Fighter

Cloud Developmet
Kit

Cloud Developmet
Kit

CDK Tools

CDK Tools

Underlying Tools

CDK Tools

Underlying Tools

Registries

CDK Tools

Underlying Tools

Registries

Under the Hood

Cloud Developmet
Kit

AWS CDK

Honorable mention but does not support c# boo!

How does this all work?

In it's most basic form CDK related tools tend to be extremely fancy template generators *

(With a number of different convenience add-ons like building docker images / uploading static assets ect...)

C#

Terraform JSON

CDK Synth

Cloudformation

YML

K8s Helm charts

* Note "CDK" in the context is on a conceptual level. AWS CDK, CDKTF and CDK8s are all entirely different  tools using the same foundational elements.

[Imperative Code]

[Declarative configurations]

C#

Terraform JSON

CDK Synth

Cloudformation

YML

K8s Helm charts

Cloudformation

K8s

Deploy Using

Deploy Using

Deploy Using

Your Stuff

👉

Your
AWS Stuff

👉

Your
K8s Stuff

👉

* Note that CDK will in some cases act outside of this model to create assets later used by these tools deployment cycles

[...]

Welcome to my talk:
A quick introduction to terraform

(*Or Open Tofu Usage and/or naming of variants of very similar tools in this context does not specifically indicate support or endorsement of the usage of one over the other.)

Who Am I?

  • Hi I'm Bryan.
  • You all look like you want to learn some Terraform / Open Tofu
  • I really really like Terraform / Open Tofu and would love to tell you all about it

Step By Step

Write 📖

Plan📝

Apply 🚀

Write 📖

Writing Terraform

Hashicorp Configuration Language 🎉

resource "some_resource" "some_block" {
  some_config_block {
    bla = "bla"
    bla2 = 1337
    bla3 = false
  }
  
  something = "something"
}

Writing Terraform

resource "aws_instance" "some_server" {
  instance_type = "t2.micro"
  [...]
}
resource "aws_instance" "some_server" {
  instance_type = "t2.micro"
  [...]
}

resource "aws_instance" "some_server2" {
  instance_type = "t2.micro"
  [...]
}
resource "aws_ami" "some_ami" {
  prefix = "someing/*"
}

resource "aws_instance" "some_server" {
  ami = aws_ami.some_ami
  instance_type = "t2.micro"
  [...]
}

resource "aws_instance" "some_server2" {
  ami = aws_ami.some_ami
  instance_type = "t2.micro"
  [...]
}

AMI

Plan 📖

Plan 📖

Terraform State
(What Terraform thinks you currently have)

Code Changes
(What you are declaring the next state of the system should be)

Compute
Difference

Plan 📖

terraform plan -out "tfplan"
aws.region: Reading...
aws.region: Read complete after 0s [id=us-east-1]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

- resource "region" "us-east-1" {
-  # The literal entire AWS Region
- }

Plan: 0 to add, 0 to change, 1 to destroy.

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"

Step By Step

Write 📖

Plan📝

Apply 🚀

Step By Step

Write 📖

Plan📝

Apply 🚀

Apply 🚀

$ terraform apply

Enter a value: yes

Creating... module.crowdsrike_channel_file[291]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

update-status = "¯\_(ツ)_/¯"

Terraform State

Terraform State

Terraform Providers

Terraform Providers

[STUFF]

Provider

Terraform Providers

[STUFF]

Provider

Terraform Providers

Terraform Providers

Thanks!

[...]

So how does c# fit into this!

Everyone knows Terraform right?

Plan📝

Apply 🚀

Write 📖

The standard flow

Synth 🤖

Plan📝

Apply 🚀

Write 📖

The cdktf flow

Synth 🤖

Synth 🤖

"jsii allows code in any language to naturally interact with JavaScript classes. It is the technology that enables the AWS Cloud Development Kit to deliver polyglot libraries from a single codebase!

A class library written in TypeScript can be used in projects authored in TypeScript or Javascript (as usual), but also in Python, Java, C# (and other languages from the .NET family), ..."

- https://github.com/aws/jsii

"jsii allows code in any language to naturally interact with JavaScript classes. It is the technology that enables the AWS Cloud Development Kit to deliver polyglot libraries from a single codebase!

A class library written in TypeScript can be used in projects authored in TypeScript or Javascript (as usual), but also in Python, Java, C# (and other languages from the .NET family), ..."

- https://github.com/aws/jsii

"jsii allows code in any language to naturally interact with JavaScript classes. It is the technology that enables the AWS Cloud Development Kit to deliver polyglot libraries from a single codebase!

A class library written in TypeScript can be used in projects authored in TypeScript or Javascript (as usual), but also in Python, Java, C# (and other languages from the .NET family), ..."

- https://github.com/aws/jsii

What is the point?

Aha!

"Constructs are classes which define a "piece of system state". Constructs can be composed together to form higher-level building blocks which represent more complex state.

Constructs are often used to represent the desired state of cloud applications. For example, in the AWS CDK, which is used to define the desired state for AWS infrastructure using CloudFormation, the lowest-level construct represents a resource definition in a CloudFormation template. These resources are composed to represent higher-level logical units of a cloud application, etc."

How are constructs written and composed?

Levels of abstraction

Level 1 Constructs:
The smallest unit of system state you can be represented and controled with cdk. Often direct one to one mapping with a resource in the provider you are using and are automatically generated by CDK tooling.

Levels of abstraction

Level 2 Constructs:
A higher level abstractions of a L1 constructs offering more functionality and work-ability when using them with other constructs. Manually coded and maintained by someone to make them more usable to everyone.

Levels of abstraction

Level 3 Constructs:
A collection L2 and L1 constructs put together into standard design patterns. For instance deploying all the resources required to host a static website

Resgistry for patterns

Registry for integrations

Bringing it all together

Testing 🧪

Conventional Unit Tests

Unit testing libraries can be used to perform unit tests on higher level constructs.

This is assisted by language specific assertion libraries that target the internal construct state allowing for checking resources were made/

Declarative Representation of Desired Infrastructure state

Declarative Representation of Desired Infrastructure state

Declarative Representation of Desired Infrastructure state

Declarative Representation of Desired Infrastructure state

Declarative Representation of Desired Infrastructure state

Declarative Representation of Current Infrastructure state

Declarative Representation of Desired Infrastructure state

Declarative Representation of Current Infrastructure state

Committed as a file

Generated from code

Diff

📸 Snapshot testing

Declarative Representation of Desired Infrastructure state

Declarative Representation of Current Infrastructure state

Committed as a file

Generated from code

Diff

📸 Snapshot testing

Deploy

Commit

Deploy

📸 Snapshot testing

Declarative Representation of Desired Infrastructure state

Declarative Representation of Current Infrastructure state

Committed as a file

Generated from code

Diff

Deploy

Commit

Deploy

Repeat

This is a Dotnet talk where is the c#

Live demo

Live demo...

(Which will 100 percent go to plan)

Would I recommend CDK

CDK TF ... No really
It has not reached a v1 release. Supports only older versions of dotnet and generally there is a lack of broader ecosystem support for L3 Constructs. Easier to learn and clearer to write Terraform with HCL.
AWS CDK Yes! ... If you are only doing stuff in AWS*  and are happy having the underlying deployment engine be cloudformation. (Cloudformation YML is a nightmare to write and maintain) Large number of first party maintained L3 constructs.
* You can manage resources outside of AWS throug the cloud control api but usage does not seem broadscale.
CDK8s ¯\_(ツ)_/¯ No Idea, have not used it

Thanks for Listening!

Questions?

(... So far)

[...]

Choose Your Fighter

Cloud Developmet
Kit

What about this?

So how does it work?

So how does it work?

So how does it work?

Surprisingly similar to CDKTF

Imperative Code

Imperative Code

Declarative Form

Convert to

Imperative Code

Declarative Form

Convert to

Plan / Preview

Diff against

existing state

Imperative Code

Declarative Form

Convert to

Plan / Preview

Diff against

existing state

Execute based on changes required

Use directed graph to execute changes in order

What Is Pullumi?

What Is Pullumi?

What Makes Pulumi Different?

(Starting with some internals)

Adds Language
Support

Handles state management / Ordering CRUD operations ect..

Your Code

Your Code

Input

Your Code

Input

👀

Its your time
to shine! ✨

Your Code

Your Code

Run this

Your Code

Child Process

gRPC

Pulumi
Resource Provider

The Real Resources

State / Secrets / Config ect..

HTTPs (normally)

Your Code

Child Process

gRPC

Pulumi
Resource Provider

The Real Resources

State / Secrets / Config ect..

HTTPs (normally)

Can I have one
    'u7in-32tb.224xlarge' for 3 years reserved all upfront please?

Your Code

Child Process

gRPC

Pulumi
Resource Provider

The Real Resources

State / Secrets / Config ect..

HTTPs (normally)

Your Code

Child Process

gRPC

Pulumi
Resource Provider

The Real Resources

State / Secrets / Config ect..

HTTPs (normally)

Did I already create one?

Your Code

Child Process

gRPC

Pulumi
Resource Provider

The Real Resources

State / Secrets / Config ect..

HTTPs (normally)

Nope

Your Code

Child Process

gRPC

Pulumi
Resource Provider

The Real Resources

State / Secrets / Config ect..

HTTPs (normally)

Repeat for all other resources to generate plan

Your Code

Child Process

gRPC

Pulumi
Resource Provider

The Real Resources

State / Secrets / Config ect..

HTTPs (normally)

gRPC

Your Code

Child Process

gRPC

Pulumi
Resource Provider

The Real Resources

State / Secrets / Config ect..

HTTPs (normally)

gRPC

Can I have one
    'u7in-32tb.224xlarge' for 3 years reserved all upfront please

Your Code

Child Process

gRPC

Pulumi
Resource Provider

The Real Resources

State / Secrets / Config ect..

HTTPs (normally)

gRPC

Your Code

Child Process

gRPC

Pulumi
Resource Provider

The Real Resources

State / Secrets / Config ect..

HTTPs (normally)

gRPC

Can I have one
    'u7in-32tb.224xlarge' for 3 years reserved all upfront please

Your Code

Child Process

gRPC

Pulumi
Resource Provider

The Real Resources

State / Secrets / Config ect..

HTTPs (normally)

gRPC

Sure that will be a be 2.6 Million dollars cash or card :)

Packaging &
Resources

Provider Registry

Provider Registry

Consumes

Packaging & Resources

Consumes

Also Consumes

[Many thousands
of providers]

Resource Repositories

Consumes

Through an Adapter
(AWS only)

[Many thousands
of "L3 Constructs"]

Native Providers

  • With Terraform providers package maintainers need to update API's in order add support for new features
  • AWS Cloudformation ironically can sometimes take months to support certain api features. If ever.
  • Pulumi automates this process by generating provider code from a cloud providers API specification. Meaning released features typically only take a day become fully supported by the provider

Codegen

The Language Host

  • Not secretly JavaScript doing all the work, Language native through codegen.
  • Coding approach aims to leverage language features over tying to morph the language into a pre-existing, possibly somewhat odd style.
  • Full purpose built integration with the "engine" over trying to repurpose an existing tool in order to do so.

A quick aside on DevEx

(Given the do so much right)

pulumi git:(main) ✗ pulumi new csharp 
pulumi git:(main) ✗ pulumi new csharp 
Manage your Pulumi stacks by logging in.
Run `pulumi login --help` for alternative login options.
Enter your access token from https://app.pulumi.com/account/tokens

😓

pulumi git:(main) ✗ pulumi new csharp 
Manage your Pulumi stacks by logging in.
Run `pulumi login --help` for alternative login options.
Enter your access token from https://app.pulumi.com/account/tokens

What is your initial reaction when you see this?

pulumi git:(main) ✗ pulumi new csharp 
Manage your Pulumi stacks by logging in.
Run `pulumi login --help` for alternative login options.
Enter your access token from https://app.pulumi.com/account/tokens

My initial reaction was...

I Need an account

How many of you would continue to try evaluating the tool after this point?

Pulumi has a tendency to push it's cloud offering

pulumi git:(main) ✗ pulumi new csharp 
Manage your Pulumi stacks by logging in.
Run `pulumi login --help` for alternative login options.
Enter your access token from https://app.pulumi.com/account/tokens
pulumi git:(main) ✗ pulumi new csharp 
Manage your Pulumi stacks by logging in.
Run `pulumi login --help` for alternative login options.
Enter your access token from https://app.pulumi.com/account/tokens

Pulumi has a tendency to push it's cloud offering

Pulumi supports two classes of state backends for storing your infrastructure state:

  • Pulumi Cloud: a managed cloud experience using the online or self-hosted Pulumi Cloud application
  • DIY backend: “Do it Yourself”- a manually managed object store, including AWS S3, Azure Blob Storage, Google Cloud Storage, any AWS S3 compatible server such as Minio or Ceph, or your local filesystem

Pulumi’s SDK works great with all backends, although some details differ between them.

https://www.pulumi.com/docs/iac/concepts/state-and-backends/
 
pulumi git:(main) ✗ pulumi new csharp 
Manage your Pulumi stacks by logging in.
Run `pulumi login --help` for alternative login options.
Enter your access token from https://app.pulumi.com/account/tokens

Run this

What you were supposed to do beyond googling it.

Log in to the Pulumi Cloud.

The Pulumi Cloud manages your stack's state reliably. Simply run

    $ pulumi login

and this command will prompt you for an access token, including a way to launch your web browser to
easily obtain one. You can script by using `PULUMI_ACCESS_TOKEN` environment variable.

By default, this will log in to the managed Pulumi Cloud backend.
If you prefer to log in to a self-hosted Pulumi Cloud backend, specify a URL. For example, run

    $ pulumi login https://api.pulumi.acmecorp.com

to log in to a self-hosted Pulumi Cloud running at the api.pulumi.acmecorp.com domain.

For `https://` URLs, the CLI will speak REST to a Pulumi Cloud that manages state and concurrency control.
You can specify a default org to use when logging into the Pulumi Cloud backend or a self-hosted Pulumi Cloud.

[PREVIEW] If you prefer to operate Pulumi independently of a Pulumi Cloud, and entirely local to your computer,
pass `file://<path>`, where `<path>` will be where state checkpoints will be stored. For instance,

    $ pulumi login file://~

will store your state information on your computer underneath `~/.pulumi`. It is then up to you to
manage this state, including backing it up, using it in a team environment, and so on.

As a shortcut, you may pass --local to use your home directory (this is an alias for `file://~`):

    $ pulumi login --local

What you were supposed to do beyond googling it.

Log in to the Pulumi Cloud.

The Pulumi Cloud manages your stack's state reliably. Simply run

    $ pulumi login

and this command will prompt you for an access token, including a way to launch your web browser to
easily obtain one. You can script by using `PULUMI_ACCESS_TOKEN` environment variable.

By default, this will log in to the managed Pulumi Cloud backend.
If you prefer to log in to a self-hosted Pulumi Cloud backend, specify a URL. For example, run

    $ pulumi login https://api.pulumi.acmecorp.com

to log in to a self-hosted Pulumi Cloud running at the api.pulumi.acmecorp.com domain.

For `https://` URLs, the CLI will speak REST to a Pulumi Cloud that manages state and concurrency control.
You can specify a default org to use when logging into the Pulumi Cloud backend or a self-hosted Pulumi Cloud.

[PREVIEW] If you prefer to operate Pulumi independently of a Pulumi Cloud, and entirely local to your computer,
pass `file://<path>`, where `<path>` will be where state checkpoints will be stored. For instance,

    $ pulumi login file://~

will store your state information on your computer underneath `~/.pulumi`. It is then up to you to
manage this state, including backing it up, using it in a team environment, and so on.

As a shortcut, you may pass --local to use your home directory (this is an alias for `file://~`):

    $ pulumi login --local

What you were supposed to do beyond googling it.

Then run this

Even if the offering is free* and likely offers a better more secure managed experience. The way Pulumi seems to handle it is a lot more "in your face" than any other tool within the space than I have used. I understand that Pulumi as a company needs to make money. And in doing so it maintains it's open source offering.

*For the first 200 resources

The way it is done is a BIG shame and looks to needlessly drives people away from the ecosystem by people assuming that an account is required.

[/rant]

Second times the charm

Running

Controlled By

The internet

Running

Controlled By

The internet

Running

Controlled By

The internet

To the code!

Comparisons and Conclusions

Cloud Developmet
Kit

Comparisons and Conclusions

Code Support

CDK (TF + AWS):

Feels typescript native with C# language support.  Locks you into a certain way of thinking ans JSii can sometimes be problematic from the perspective of other languages.

Pulumi:

A more C# native with other languages as an option. Less of a requirement to understand the underlying mechanisms that make up the tooling.

Comparisons and Conclusions

Backend State / Cloud offering

CDK (TF): Terraform cloud is available charged per resource. Handles Locking / state storage / secrets. Self hosted alternatives available

Pulumi: Pulumi cloud is available charged per resource. Handles Locking / state storage / secrets. Self hosted alternatives available

CDK (AWS): Cloudformation internally manages state / locking / secrets but can only be used (Easily) in conjunction with other AWS Services

Comparisons and Conclusions

Overall Thoughts

CDK (TF): Very cool Idea. If you already use terraform. Possibly worth looking into though probably easier to just use HCL.

Pulumi: Very good broad scope support for languages. Would possibly investigate over CDKTF unless you already use terraform for a significant amount of your infrastructure.

CDK (AWS): A billion time better than writing raw cloud formation templates. Very powerful for using AWS

Comparisons and Conclusions

More reading

cdk-tf

aws-cdk

pulumi

Credits

CC0 Starry Skydancer
Attribution-ShareAlike: Kelly Cookson
CC0 1.0 Universal license.

https://techwatching.dev/posts/flex-consumption-plan

Images

Live demo

Thank you and any questions?

Dot Net User Group 2025

By Rizza

Dot Net User Group 2025

  • 68