Skip to content

Commit 36c86ad

Browse files
committed
Add a Tutorials section
1 parent 92530cf commit 36c86ad

File tree

114 files changed

+9986
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+9986
-0
lines changed

content/tutorials/_index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: Tutorials
3+
meta_desc: Here is the amazing meta description. It's more than 50 characters but less than 160.
4+
---
5+
6+
Here is a line or two to explain where you are and what you'll find here.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
title_tag: Abstraction & Encapsulation Overview | Learn Pulumi
3+
title: "Abstraction and Encapsulation"
4+
layout: module
5+
date: 2021-11-17
6+
description: |
7+
Explore abstraction and encapsulation with Pulumi and component resources.
8+
meta_desc: In this tutorial, we will explore using Pulumi to abstract and encapsulate your resource definitions as reusable models for others to use.
9+
index: 6
10+
meta_image: meta.png
11+
level: intermediate
12+
youll_learn:
13+
- Encapsulating Pulumi components
14+
- Making reusable abstractions
15+
- Building your own resources
16+
tags:
17+
- learn
18+
- components
19+
providers:
20+
- aws
21+
---
22+
23+
## Time
24+
25+
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.
26+
27+
## Prerequisites
28+
29+
You will need the following knowledge to complete this pathway:
30+
31+
- 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
32+
33+
Optionally, if you want to build some of the examples yourself to experiment, you'll need the following tools and skills:
34+
35+
- A [Pulumi account and token](/docs/pulumi-cloud/accounts#access-tokens)
36+
- If you don't have an account, go to the [signup page](https://app.pulumi.com/signup).
37+
- The [Pulumi CLI](/docs/cli/commands/)
38+
- If you don't have the CLI, go to the [installation page](/docs/install/).
39+
- An AWS account (the free version should be fine)
40+
- Familiarity with either the TypeScript or Python programming language
41+
42+
## About this pathway
43+
44+
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.
Loading
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
title_tag: Abstracting Pulumi Code | Learn Pulumi
3+
title: "Abstracting Pulumi Code"
4+
layout: topic
5+
date: 2021-11-17
6+
draft: false
7+
description: |
8+
Learn more about abstracting Pulumi code into classes, models, and objects.
9+
meta_desc: In this tutorial, we'll cover the concept of abstraction and how to abstract Pulumi code into reusable classes, models, and objects.
10+
index: 1
11+
estimated_time: 5
12+
meta_image: meta.png
13+
authors:
14+
- laura-santamaria
15+
tags:
16+
- learn
17+
---
18+
19+
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.
20+
21+
## Thinking in abstraction
22+
23+
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.
24+
25+
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.
26+
27+
## Exploring an example
28+
29+
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.
30+
31+
Mentally, we're defining an abstract cloud storage system like this:
32+
33+
{{< 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" >}}
34+
35+
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.
36+
37+
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:
38+
39+
{{< chooser language "typescript,python" />}}
40+
41+
{{% choosable language typescript %}}
42+
43+
```typescript
44+
// ...
45+
46+
type SupportedCloud = "aws" | "gcp";
47+
48+
class StorageClass {
49+
constructor(cloud: SupportedCloud) {
50+
switch (cloud) {
51+
case "aws":
52+
// Create AWS storage resources...
53+
break;
54+
case "gcp":
55+
// Create Google Cloud storage resources...
56+
break;
57+
}
58+
}
59+
}
60+
```
61+
62+
{{% /choosable %}}
63+
64+
{{% choosable language python %}}
65+
66+
```python
67+
# ...
68+
69+
class StorageClass:
70+
def __init__(self, cloud):
71+
cloud = self.cloud
72+
73+
if cloud is 'aws':
74+
# aws class here
75+
elif cloud is 'gcp:
76+
# gcp class here
77+
elif ...
78+
```
79+
80+
{{% /choosable %}}
81+
82+
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!
Loading
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
---
2+
title_tag: Building a Component Resource | Learn Pulumi
3+
title: "Building a Component Resource"
4+
layout: topic
5+
date: 2021-11-17
6+
draft: false
7+
description: |
8+
Try creating a component resource, or a logical grouping of code that Pulumi
9+
recognizes as a resource.
10+
meta_desc: Learn how to build a component resource, or a logical grouping of code that Pulumi recognizes as a resource, in this tutorial.
11+
index: 3
12+
estimated_time: 10
13+
meta_image: meta.png
14+
authors:
15+
- laura-santamaria
16+
tags:
17+
- learn
18+
links:
19+
- text: Component Resources
20+
url: https://www.pulumi.com/docs/concepts/resources/#components
21+
---
22+
23+
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.
24+
25+
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.
26+
27+
## Deciding to create a component resource
28+
29+
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.
30+
31+
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.
32+
33+
## Converting to a component resource
34+
35+
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.
36+
37+
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.
38+
39+
{{< chooser language "typescript,python" />}}
40+
41+
{{% choosable language typescript %}}
42+
43+
```typescript
44+
// ...
45+
46+
// Create a class that encapsulates the functionality by subclassing
47+
// pulumi.ComponentResource.
48+
class OurBucketComponent extends pulumi.ComponentResource {
49+
public bucket: aws.s3.Bucket;
50+
private bucketPolicy: aws.s3.BucketPolicy;
51+
52+
private policies: { [K in PolicyType]: aws.iam.PolicyStatement } = {
53+
default: {
54+
Effect: "Allow",
55+
Principal: "*",
56+
Action: [
57+
"s3:GetObject"
58+
],
59+
},
60+
locked: {
61+
/* ... */
62+
},
63+
permissive: {
64+
/* ... */
65+
},
66+
};
67+
68+
private getBucketPolicy(policyType: PolicyType): aws.iam.PolicyDocument {
69+
return {
70+
Version: "2012-10-17",
71+
Statement: [{
72+
...this.policies[policyType],
73+
Resource: [
74+
pulumi.interpolate`${this.bucket.arn}/*`,
75+
],
76+
}],
77+
}
78+
};
79+
80+
constructor(name: string, args: { policyType: PolicyType }, opts?: pulumi.ComponentResourceOptions) {
81+
82+
// By calling super(), we ensure any instantiation of this class
83+
// inherits from the ComponentResource class so we don't have to
84+
// declare all the same things all over again.
85+
super("pkg:index:OurBucketComponent", name, args, opts);
86+
87+
this.bucket = new aws.s3.Bucket(name, {}, { parent: this });
88+
89+
this.bucketPolicy = new aws.s3.BucketPolicy(`${name}-policy`, {
90+
bucket: this.bucket.id,
91+
policy: this.getBucketPolicy(args.policyType),
92+
}, { parent: this });
93+
94+
// We also need to register all the expected outputs for this
95+
// component resource that will get returned by default.
96+
this.registerOutputs({
97+
bucketName: this.bucket.id,
98+
});
99+
}
100+
}
101+
102+
const bucket = new OurBucketComponent("laura-bucket-1", {
103+
policyType: "permissive",
104+
});
105+
106+
export const bucketName = bucket.bucket.id;
107+
```
108+
109+
{{% /choosable %}}
110+
111+
{{% choosable language python %}}
112+
113+
```python
114+
import ...
115+
116+
# Create a class that encapsulates the functionality while subclassing the ComponentResource class (using the ComponentResource class as a template).
117+
class OurBucketComponent(pulumi.ComponentResource):
118+
def __init__(self, name_me, policy_name='default', opts=None):
119+
# 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.
120+
super().__init__('pkg:index:OurBucketComponent', name, None, opts)
121+
# This definition ensures the new component resource acts like anything else in the Pulumi ecosystem when being called in code.
122+
child_opts = pulumi.ResourceOptions(parent=self)
123+
self.name_me = name_me
124+
self.policy_name = policy_name
125+
self.bucket = aws_native.s3.Bucket(f"{self.name_me}")
126+
self.policy_list = {
127+
'default': default,
128+
'locked': '{...}',
129+
'permissive': '{...}'
130+
}
131+
# We also need to register all the expected outputs for this component resource that will get returned by default.
132+
self.register_outputs({
133+
"bucket_name": self.bucket.bucket_name
134+
})
135+
136+
def define_policy(self):
137+
policy_name = self.policy_name
138+
try:
139+
json_data = self.policy_list[f"{policy_name}"]
140+
policy = self.bucket.arn.apply(lambda arn: json.dumps(json_data).replace('fakeobjectresourcething', arn))
141+
return policy
142+
except KeyError as err:
143+
add_note = "Policy name needs to be 'default', 'locked', or 'permissive'"
144+
print(f"Error: {add_note}. You used {policy_name}.")
145+
raise
146+
147+
def set_policy(self):
148+
bucket_policy = aws_classic.s3.BucketPolicy(
149+
f"{self.name_me}-policy",
150+
bucket=self.bucket.id,
151+
policy=self.define_policy(),
152+
opts=pulumi.ResourceOptions(parent=self.bucket)
153+
)
154+
return bucket_policy
155+
156+
bucket1 = OurBucketClass('laura-bucket-1', 'default')
157+
bucket1.set_policy()
158+
159+
pulumi.export("bucket_name", bucket1.bucket.id)
160+
```
161+
162+
{{% /choosable %}}
163+
164+
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`).
165+
166+
{{% choosable language python %}}
167+
168+
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.
169+
170+
{{% /choosable %}}
171+
172+
{{% choosable language typescript %}}
173+
174+
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.
175+
176+
{{% /choosable %}}
177+
178+
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.
179+
180+
---
181+
182+
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.
183+
184+
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:
185+
186+
* [An AWS/Python example](https://github.com/pulumi/examples/tree/master/aws-py-wordpress-fargate-rds)
187+
* [An Azure/Python example](https://github.com/pulumi/examples/tree/master/classic-azure-py-webserver-component)
188+
* [A Google Cloud/Python example](https://github.com/pulumi/examples/tree/master/gcp-py-network-component)
189+
190+
Go build new things, and watch this space for more learning experiences with Pulumi!
Loading

0 commit comments

Comments
 (0)