-
Notifications
You must be signed in to change notification settings - Fork 230
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
114 changed files
with
9,986 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
title: Tutorials | ||
meta_desc: Here is the amazing meta description. It's more than 50 characters but less than 160. | ||
--- | ||
|
||
Here is a line or two to explain where you are and what you'll find here. |
44 changes: 44 additions & 0 deletions
44
content/tutorials/modules/abstraction-encapsulation/_index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
--- | ||
title_tag: Abstraction & Encapsulation Overview | Learn Pulumi | ||
title: "Abstraction and Encapsulation" | ||
layout: module | ||
date: 2021-11-17 | ||
description: | | ||
Explore abstraction and encapsulation with Pulumi and component resources. | ||
meta_desc: In this tutorial, we will explore using Pulumi to abstract and encapsulate your resource definitions as reusable models for others to use. | ||
index: 6 | ||
meta_image: meta.png | ||
level: intermediate | ||
youll_learn: | ||
- Encapsulating Pulumi components | ||
- Making reusable abstractions | ||
- Building your own resources | ||
tags: | ||
- learn | ||
- components | ||
providers: | ||
- aws | ||
--- | ||
|
||
## Time | ||
|
||
How long this tutorial will take depends on your internet connection, reading speed, and other factors. On average, this tutorial should take you about 25 minutes to complete. | ||
|
||
## Prerequisites | ||
|
||
You will need the following knowledge to complete this pathway: | ||
|
||
- Completion of the [Pulumi Fundamentals](/learn/pulumi-fundamentals/) and [Building with Pulumi](/learn/building-with-pulumi/) pathways, OR have experience using Pulumi for other projects | ||
|
||
Optionally, if you want to build some of the examples yourself to experiment, you'll need the following tools and skills: | ||
|
||
- A [Pulumi account and token](/docs/pulumi-cloud/accounts#access-tokens) | ||
- If you don't have an account, go to the [signup page](https://app.pulumi.com/signup). | ||
- The [Pulumi CLI](/docs/cli/commands/) | ||
- If you don't have the CLI, go to the [installation page](/docs/install/). | ||
- An AWS account (the free version should be fine) | ||
- Familiarity with either the TypeScript or Python programming language | ||
|
||
## About this pathway | ||
|
||
In this pathway, we will explore how you can use Pulumi as a tool to help you abstract and encapsulate your resource definitions as reusable models for others to use. |
Binary file added
BIN
+51.7 KB
...tutorials/modules/abstraction-encapsulation/abstraction/abstraction-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 82 additions & 0 deletions
82
content/tutorials/modules/abstraction-encapsulation/abstraction/index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
--- | ||
title_tag: Abstracting Pulumi Code | Learn Pulumi | ||
title: "Abstracting Pulumi Code" | ||
layout: topic | ||
date: 2021-11-17 | ||
draft: false | ||
description: | | ||
Learn more about abstracting Pulumi code into classes, models, and objects. | ||
meta_desc: In this tutorial, we'll cover the concept of abstraction and how to abstract Pulumi code into reusable classes, models, and objects. | ||
index: 1 | ||
estimated_time: 5 | ||
meta_image: meta.png | ||
authors: | ||
- laura-santamaria | ||
tags: | ||
- learn | ||
--- | ||
|
||
Just like any application code, Pulumi infrastructure code can be abstracted, enabling us to work with high-level, generic representations of the things we need to build. If we use an object-oriented language such as JavaScript, TypeScript, Python, C#, or Java, we can create and instantiate classes. For languages like Go, we can build up interfaces. In all cases, we're thinking in terms of taking code that we've written and making it reusable in some form. Let's first explore abstraction. | ||
|
||
## Thinking in abstraction | ||
|
||
When we use our code for our Pulumi program, we're generally starting out with a very basic case. Perhaps we're building out a general proof-of-concept, or a one-off architecture for learning purposes. In those cases, keeping all of the code in a single file with no classes, functions, or other structures probably is enough. | ||
|
||
To get to more complex architectures, we need the ability to break apart that code so we can reuse components and call things in any order we choose, knowing that we will get the expected result each time. We also need to allow others to use parts of our code without necessarily needing to know precisely how it's put together. This abstractionist way of thinking means we're thinking in terms of the mental models we use to understand the cloud architectures that are available to us—or what we want to make available to others in our organization. | ||
|
||
## Exploring an example | ||
|
||
In the last part of this pathway, we took a simple S3 bucket build and broke out the code into reusable parts. As a build, it's still dependent on the structures built around an S3 bucket. Let's imagine the Pulumipus brand is expanding to all kinds of systems, and so we don't know which infrastructure each team will need. Could we reuse the ideas we just explored? Well, each team will need some kind of storage for objects that has access policies and security controls, no matter whether they're doing a serverless build or a VM-based build. Believe it or not, that mental model, or that abstraction, is nearly identical to the mental model we just built up for S3 buckets, just without depending on the structures of AWS. | ||
|
||
Mentally, we're defining an abstract cloud storage system like this: | ||
|
||
{{< figure alt="A diagram showing a user doing an action that touches the storage through a policy" src="./abstraction-diagram.png" width=100% attr="Drawn with draw.io" >}} | ||
|
||
Seems pretty clean, doesn't it? Abstraction is thinking through what we experience when we call the code, so that anyone calling that code experiences the same thing. The part that gets complicated is when we start translating that idea to code. To abstract something away, we provide a higher-level interface for someone to use to request that experience overall without needing to know all of the low-level details regarding how that experience is put together, such as which policies are necessary to get the desired result. | ||
|
||
With Pulumi, one way to abstract the prior work away to match this diagram a bit better might be to wrap the whole thing in another class that requires you to define which cloud provider you want to use, kind of like this pseudo code layout: | ||
|
||
{{< chooser language "typescript,python" />}} | ||
|
||
{{% choosable language typescript %}} | ||
|
||
```typescript | ||
// ... | ||
|
||
type SupportedCloud = "aws" | "gcp"; | ||
|
||
class StorageClass { | ||
constructor(cloud: SupportedCloud) { | ||
switch (cloud) { | ||
case "aws": | ||
// Create AWS storage resources... | ||
break; | ||
case "gcp": | ||
// Create Google Cloud storage resources... | ||
break; | ||
} | ||
} | ||
} | ||
``` | ||
|
||
{{% /choosable %}} | ||
|
||
{{% choosable language python %}} | ||
|
||
```python | ||
# ... | ||
|
||
class StorageClass: | ||
def __init__(self, cloud): | ||
cloud = self.cloud | ||
|
||
if cloud is 'aws': | ||
# aws class here | ||
elif cloud is 'gcp: | ||
# gcp class here | ||
elif ... | ||
``` | ||
|
||
{{% /choosable %}} | ||
|
||
There's a cleaner way to do this, though, with Pulumi if you want to start to share these abstractions with others. We call them _Component Resources_. Let's go explore them next! |
Binary file added
BIN
+120 KB
content/tutorials/modules/abstraction-encapsulation/abstraction/meta.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
190 changes: 190 additions & 0 deletions
190
content/tutorials/modules/abstraction-encapsulation/component-resources/index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
--- | ||
title_tag: Building a Component Resource | Learn Pulumi | ||
title: "Building a Component Resource" | ||
layout: topic | ||
date: 2021-11-17 | ||
draft: false | ||
description: | | ||
Try creating a component resource, or a logical grouping of code that Pulumi | ||
recognizes as a resource. | ||
meta_desc: Learn how to build a component resource, or a logical grouping of code that Pulumi recognizes as a resource, in this tutorial. | ||
index: 3 | ||
estimated_time: 10 | ||
meta_image: meta.png | ||
authors: | ||
- laura-santamaria | ||
tags: | ||
- learn | ||
links: | ||
- text: Component Resources | ||
url: https://www.pulumi.com/docs/concepts/resources/#components | ||
--- | ||
|
||
Now that we have a better understanding of why abstraction and encapsulation are valuable concepts for Pulumi programs, let's explore what happens when we start working in larger teams at scale. | ||
|
||
A resource in Pulumi terms is basically a building block. That building block could be a base component like an `s3.Bucket` object that's managed by external providers like your favorite cloud provider. Alternatively, a resource could be a group of other resources that implements abstraction and encapsulation for reasons like defining a common pattern. This latter type of resource is called a [_Component Resource_](/docs/concepts/resources#components) in Pulumi's terminology. | ||
|
||
## Deciding to create a component resource | ||
|
||
Why would you make a component resource instead of just a "simple" logical grouping that you can call and use like a generic library full of classes and functions? A component resource shows up in the Pulumi ecosystem just like any other resource. That means it has a trackable state, appears in diffs, and has a name field you can reference that refers to the entire grouping. | ||
|
||
We've actually already started creating a component resource in the encapsulation part when we made a new class that built up an `s3.Bucket` and an `s3.BucketPolicy`. Let's now turn that logical grouping into an actual component resource. | ||
|
||
## Converting to a component resource | ||
|
||
When we're converting to a component resource, we're subclassing the `ComponentResource` so that our new component resource can get all of the lovely benefits of a resource (state tracking, diffs, name fields, etc.) that other resources have. | ||
|
||
In Python, we subclass by using `super()` in the initialization of the class. This call ensures that Pulumi registers the component resource as a resource properly. | ||
|
||
{{< chooser language "typescript,python" />}} | ||
|
||
{{% choosable language typescript %}} | ||
|
||
```typescript | ||
// ... | ||
|
||
// Create a class that encapsulates the functionality by subclassing | ||
// pulumi.ComponentResource. | ||
class OurBucketComponent extends pulumi.ComponentResource { | ||
public bucket: aws.s3.Bucket; | ||
private bucketPolicy: aws.s3.BucketPolicy; | ||
|
||
private policies: { [K in PolicyType]: aws.iam.PolicyStatement } = { | ||
default: { | ||
Effect: "Allow", | ||
Principal: "*", | ||
Action: [ | ||
"s3:GetObject" | ||
], | ||
}, | ||
locked: { | ||
/* ... */ | ||
}, | ||
permissive: { | ||
/* ... */ | ||
}, | ||
}; | ||
|
||
private getBucketPolicy(policyType: PolicyType): aws.iam.PolicyDocument { | ||
return { | ||
Version: "2012-10-17", | ||
Statement: [{ | ||
...this.policies[policyType], | ||
Resource: [ | ||
pulumi.interpolate`${this.bucket.arn}/*`, | ||
], | ||
}], | ||
} | ||
}; | ||
|
||
constructor(name: string, args: { policyType: PolicyType }, opts?: pulumi.ComponentResourceOptions) { | ||
|
||
// By calling super(), we ensure any instantiation of this class | ||
// inherits from the ComponentResource class so we don't have to | ||
// declare all the same things all over again. | ||
super("pkg:index:OurBucketComponent", name, args, opts); | ||
|
||
this.bucket = new aws.s3.Bucket(name, {}, { parent: this }); | ||
|
||
this.bucketPolicy = new aws.s3.BucketPolicy(`${name}-policy`, { | ||
bucket: this.bucket.id, | ||
policy: this.getBucketPolicy(args.policyType), | ||
}, { parent: this }); | ||
|
||
// We also need to register all the expected outputs for this | ||
// component resource that will get returned by default. | ||
this.registerOutputs({ | ||
bucketName: this.bucket.id, | ||
}); | ||
} | ||
} | ||
|
||
const bucket = new OurBucketComponent("laura-bucket-1", { | ||
policyType: "permissive", | ||
}); | ||
|
||
export const bucketName = bucket.bucket.id; | ||
``` | ||
|
||
{{% /choosable %}} | ||
|
||
{{% choosable language python %}} | ||
|
||
```python | ||
import ... | ||
|
||
# Create a class that encapsulates the functionality while subclassing the ComponentResource class (using the ComponentResource class as a template). | ||
class OurBucketComponent(pulumi.ComponentResource): | ||
def __init__(self, name_me, policy_name='default', opts=None): | ||
# By calling super(), we ensure any instantiation of this class inherits from the ComponentResource class so we don't have to declare all the same things all over again. | ||
super().__init__('pkg:index:OurBucketComponent', name, None, opts) | ||
# This definition ensures the new component resource acts like anything else in the Pulumi ecosystem when being called in code. | ||
child_opts = pulumi.ResourceOptions(parent=self) | ||
self.name_me = name_me | ||
self.policy_name = policy_name | ||
self.bucket = aws_native.s3.Bucket(f"{self.name_me}") | ||
self.policy_list = { | ||
'default': default, | ||
'locked': '{...}', | ||
'permissive': '{...}' | ||
} | ||
# We also need to register all the expected outputs for this component resource that will get returned by default. | ||
self.register_outputs({ | ||
"bucket_name": self.bucket.bucket_name | ||
}) | ||
|
||
def define_policy(self): | ||
policy_name = self.policy_name | ||
try: | ||
json_data = self.policy_list[f"{policy_name}"] | ||
policy = self.bucket.arn.apply(lambda arn: json.dumps(json_data).replace('fakeobjectresourcething', arn)) | ||
return policy | ||
except KeyError as err: | ||
add_note = "Policy name needs to be 'default', 'locked', or 'permissive'" | ||
print(f"Error: {add_note}. You used {policy_name}.") | ||
raise | ||
|
||
def set_policy(self): | ||
bucket_policy = aws_classic.s3.BucketPolicy( | ||
f"{self.name_me}-policy", | ||
bucket=self.bucket.id, | ||
policy=self.define_policy(), | ||
opts=pulumi.ResourceOptions(parent=self.bucket) | ||
) | ||
return bucket_policy | ||
|
||
bucket1 = OurBucketClass('laura-bucket-1', 'default') | ||
bucket1.set_policy() | ||
|
||
pulumi.export("bucket_name", bucket1.bucket.id) | ||
``` | ||
|
||
{{% /choosable %}} | ||
|
||
With the call to `super()`, we pass in a name for the resource, which [we recommend](/docs/concepts/resources/components#authoring-a-new-component-resource) being of the form `<package>:<module>:<type>` to avoid type conflicts since it's being registered alongside other resources like the Bucket resource we're calling (`aws:s3:Bucket`). | ||
|
||
{{% choosable language python %}} | ||
|
||
That last call in the init, `self.register_outputs({})`, passes Pulumi the expected outputs so Pulumi can read the results of the creation or update of a component resource just like any other resource, so don't forget that call! You can [register default outputs using this call](/docs/concepts/resources/components#registering-component-outputs), as well. It's not hard to imagine we will always want the bucket name for our use case, so we pass that in as an always-given output for our component resource. | ||
|
||
{{% /choosable %}} | ||
|
||
{{% choosable language typescript %}} | ||
|
||
That last call in the init, `this.registerOutputs({})`, passes Pulumi the expected outputs so Pulumi can read the results of the creation or update of a component resource just like any other resource, so don't forget that call! You can [register default outputs using this call](/docs/concepts/resources/components#registering-component-outputs), as well. It's not hard to imagine we will always want the bucket name for our use case, so we pass that in as an always-given output for our component resource. | ||
|
||
{{% /choosable %}} | ||
|
||
From here, you can deploy it and get your custom resource appearing in the resource tree in your terminal! You can also share it with others so they can import the resource and use it without ever needing to understand all of the underlying needs of a standard storage system on AWS. | ||
|
||
--- | ||
|
||
Congratulations! You've now finished this pathway on abstraction and encapsulation in Pulumi programs! In this pathway, you've learned about thinking of code in abstract forms, wrapping up logical groupings of code to make reuse easier, and building with component resources to make those logical groupings something that Pulumi recognizes. | ||
|
||
There's a lot more to explore regarding this topic in Pulumi. We're working on more pathways, but for now, check out some more resources: | ||
|
||
* [An AWS/Python example](https://github.com/pulumi/examples/tree/master/aws-py-wordpress-fargate-rds) | ||
* [An Azure/Python example](https://github.com/pulumi/examples/tree/master/classic-azure-py-webserver-component) | ||
* [A Google Cloud/Python example](https://github.com/pulumi/examples/tree/master/gcp-py-network-component) | ||
|
||
Go build new things, and watch this space for more learning experiences with Pulumi! |
Binary file added
BIN
+120 KB
content/tutorials/modules/abstraction-encapsulation/component-resources/meta.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.