diff --git a/src/content/docs/pulumi/tutorial/add-site.mdx b/src/content/docs/pulumi/tutorial/add-site.mdx index c3a5d406c46956..a9321ed6bae090 100644 --- a/src/content/docs/pulumi/tutorial/add-site.mdx +++ b/src/content/docs/pulumi/tutorial/add-site.mdx @@ -9,7 +9,8 @@ languages: - Java - .NET - YAML -updated: 2024-08-02 +updated: 2024-09-13 +difficulty: Beginner sidebar: order: 1 head: @@ -19,10 +20,9 @@ head: import { TabItem, Tabs } from "~/components"; -In this tutorial, you will go through step-by-step instructions to bring an existing site to Cloudflare using Pulumi Infrastructure as Code (IaC) so that you can become familiar with the resource management lifecycle. In particular, you will create a Zone and a DNS record to resolve your newly added site. This tutorial adopts the IaC principle to complete the steps listed in the [Add site tutorial](/fundamentals/setup/manage-domains/add-site/). +In this tutorial, you will follow step-by-step instructions to bring an existing site to Cloudflare using Pulumi infrastructure as code (IaC) to familiarize yourself with the resource management lifecycle. In particular, you will create a Zone and a DNS record to resolve your newly added site. This tutorial adopts the IaC principle to complete the steps listed in the [Add site tutorial](/fundamentals/setup/manage-domains/add-site/). :::note - You will provision resources that qualify under free tier offerings for both Pulumi Cloud and Cloudflare. ::: @@ -30,16 +30,18 @@ You will provision resources that qualify under free tier offerings for both Pul Ensure you have: -- A Cloudflare account and API Token with permission to edit the resources in this tutorial. If you need to, sign up for a [Cloudflare account](https://dash.cloudflare.com/sign-up/workers-and-pages) before continuing. Your token must have: +- A Cloudflare account and API Token with permission to edit the resources in this tutorial. If you need to, sign up for a [Cloudflare account](https://www.cloudflare.com/sign-up) before continuing. Your token must have: - `Zone-Zone-Edit` permission - `Zone-DNS-Edit` permission - `include-All zones from an account-` zone resource -- A Pulumi Cloud account. You can sign up for an [always-free, individual tier](https://app.pulumi.com/signup). +- A Pulumi Cloud account. You can sign up for an [always-free individual tier](https://app.pulumi.com/signup). - The [Pulumi CLI](/pulumi/installing/) is installed on your machine. - A [Pulumi-supported programming language](https://github.com/pulumi/pulumi?tab=readme-ov-file#languages) is configured. (TypeScript, JavaScript, Python, Go, .NET, Java, or use YAML) - A domain name. You may use `example.com` to complete the tutorial. -## Initialize Pulumi +## 1. Initialize your project + +A Pulumi project is a collection of files in a dedicated folder that describes the infrastructure you want to create. The Pulumi project folder is identified by the required `Pulumi.yaml` file. You will use the Pulumi CLI to create and configure a new project. ### a. Create a directory @@ -50,7 +52,9 @@ mkdir addsite-cloudflare cd addsite-cloudflare ``` -### b. Login +### b. Login to Pulumi Cloud + +[Pulumi Cloud](https://www.pulumi.com/product/pulumi-cloud/) is a hosted service that provides a secure and scalable platform for managing your infrastructure as code. You will use it to store your Pulumi backend configurations. At the prompt, press Enter to log into your Pulumi Cloud account via the browser. Alternatively, you may provide a [Pulumi Cloud access token](https://www.pulumi.com/docs/pulumi-cloud/access-management/access-tokens/). @@ -58,12 +62,9 @@ At the prompt, press Enter to log into your Pulumi Cloud account via the browser pulumi login ``` -### c. Create a program - -:::note +### c. Create a new program A Pulumi program is code written in a [supported programming language](https://github.com/pulumi/pulumi?tab=readme-ov-file#languages) that defines infrastructure resources. -::: To create a program, select your language of choice and run the `pulumi` command: @@ -117,72 +118,55 @@ pulumi new yaml --name addsite-cloudflare --yes -### d. Save your settings +### d. Create a stack + +A Pulumi [stack](https://www.pulumi.com/docs/concepts/stack/) is an instance of a Pulumi program. Stacks are independently configurable and may represent different environments (development, staging, production) or feature branches. For this tutorial, you'll use the `dev` stack. + +To instantiate your `dev` stack, run: + +```sh +pulumi up --yes +# wait a few seconds for the stack to be instantiated. +``` + +You have not defined any resources at this point, so you'll have an empty stack. -You will need: +### e. Save your settings + +In this step, you will store your settings in a Pulumi [ESC Environment](https://www.pulumi.com/docs/esc/environments/), a YAML file containing configurations and secrets. These can be accessed in several ways, including a Pulumi program. All ESC Environments securely reside in your Pulumi Cloud account and can be fully managed via the Pulumi CLI. For this tutorial, you will store the following values: - Your Cloudflare [account ID](/fundamentals/setup/find-account-and-zone-ids/). - A valid Cloudflare API [token](/fundamentals/api/get-started/create-token/). - A domain. For instance, `example.com`. -:::note - -A Pulumi [ESC Environment](https://www.pulumi.com/docs/esc/environments/) is a YAML file containing configurations and secrets that pertain to your application and infrastructure. These can be accessed in several ways, including a Pulumi program. All ESC Environments reside in your Pulumi Cloud account. -::: - ```sh # Define an ESC Environment name -E=my-dev-env +E=clouflare/my-dev-env # Create a new Pulumi ESC Environment -pulumi config env init --env $E --yes --stack dev +pulumi config env init --env $E --yes +``` -# Replace API_TOKEN with your Cloudflare API Token -pulumi env set $E --secret pulumiConfig.cloudflare:apiToken API_TOKEN +```sh output +Creating environment clouflare/my-dev-env for stack dev... +``` +```sh # Replace abc123 with your Cloudflare Account ID pulumi env set $E --plaintext pulumiConfig.accountId abc123 +# Replace API_TOKEN with your Cloudflare API Token +pulumi env set $E --secret pulumiConfig.cloudflare:apiToken API_TOKEN + # Replace example.com with your registered domain, or leave as is pulumi env set $E --plaintext pulumiConfig.domain example.com - -# Review your ESC Environment -pulumi env open $E -{ - "pulumiConfig": { - "accountId": "111222333", - "cloudflare:apiToken": "abc123abc123", - "domain": "example.com" - } -} -``` - -### e. Create a stack - -:::note - -A Pulumi [stack](https://www.pulumi.com/docs/concepts/stack/) is an instance of a Pulumi program. Stacks are independently configurable and may represent different environments (development, staging, production) or feature branches. For this tutorial, you'll use the `dev` stack. -::: - -To instantiate your `dev` stack, run: - -```sh -pulumi up --yes --stack dev -# wait a few seconds for the stack to be instantiated. ``` -At this point, you have not defined any resources so you'll have an empty stack. - -## Add a Zone +### f. Install the Cloudflare package -:::note - -A domain, or site, is known as a Zone in Cloudflare. -::: +You need to install the Cloudflare package for your language of choice in order to define Cloudflare resources in your Pulumi program. -You will now add the Pulumi Cloudflare package and a Cloudflare Zone resource to your Pulumi program. - -### a. Install dependencies +Install the Cloudflare package by running the following command: @@ -207,15 +191,13 @@ added 1 package ... ```sh -echo "pulumi_cloudflare>=5.35,<6.0.0" >> requirements.txt +echo "pulumi_cloudflare>=5.38,<6.0.0" >> requirements.txt source venv/bin/activate pip install -r requirements.txt ``` ```sh output -... -Collecting pulumi-cloudflare -... +...Collecting pulumi-cloudflare... ``` @@ -239,20 +221,18 @@ Below are Apache Maven instructions. For other Java project managers such as Gra com.pulumi cloudflare - 5.35.1 + 5.38.0 ``` -1. Run: +3. Run: ```sh mvn clean install ``` ```sh output -... -[INFO] BUILD SUCCESS -... +...[INFO] BUILD SUCCESS... ``` @@ -273,12 +253,20 @@ There are no dependencies to download for YAML. Skip ahead. -### b. Modify the program +## 2. Define Cloudflare resources in code + +With the Cloudflare package installed, you can now define any [supported Cloudflare resource](https://www.pulumi.com/registry/packages/cloudflare/) in your Pulumi program. You'll define a Zone, and a DNS Record next. + +### a. Add a Zone + +A domain, or site, is known as a Zone in Cloudflare. In Pulumi, the [Zone resource](https://www.pulumi.com/registry/packages/cloudflare/api-docs/zone/) represents a Cloudflare Zone. Replace the contents of your entrypoint file with the following: +**Filename: `index.js`** + ```js "use strict"; const pulumi = require("@pulumi/pulumi"); @@ -296,12 +284,15 @@ const zone = new cloudflare.Zone("my-zone", { jumpStart: true, }); -// Export the zone ID exports.zoneId = zone.id; +exports.nameservers = zone.nameServers; +exports.status = zone.status; ``` +**Filename: `index.ts`** + ```typescript import * as pulumi from "@pulumi/pulumi"; import * as cloudflare from "@pulumi/cloudflare"; @@ -318,12 +309,15 @@ const zone = new cloudflare.Zone("my-zone", { jumpStart: true, }); -// Export the zone ID export const zoneId = zone.id; +export const nameservers = zone.nameServers; +export const status = zone.status; ``` +**Filename: `__main__.py`** + ```py import pulumi import pulumi_cloudflare as cloudflare @@ -338,12 +332,15 @@ zone = cloudflare.Zone("my-zone", plan="free", jump_start=True) -# Export the zone ID pulumi.export("zoneId", zone.id) +pulumi.export('nameservers', zone.name_servers) +pulumi.export('status', zone.status) ``` +**Filename: `main.go`** + ```go package main @@ -366,8 +363,9 @@ func main() { return err } - // Export the zone ID ctx.Export("zoneId", zone.ID()) + ctx.Export("nameservers", zone.NameServers) + ctx.Export("status", zone.Status) return nil }) } @@ -375,7 +373,7 @@ func main() { -The entrypoint file is under the `src/main/java/myproject` directory. +**Filename: `src/main/java/myproject/App.java`** ```java package myproject; @@ -402,6 +400,8 @@ public class App { .build()); ctx.export("zoneId", zone.id()); + ctx.export("nameservers", zone.nameServers()); + ctx.export("status", zone.status()); }); } } @@ -409,8 +409,11 @@ public class App { +**Filename: `Program.cs`** + ```csharp using System.Threading.Tasks; +using System.Collections.Immutable; using Pulumi; using Pulumi.Cloudflare; @@ -435,20 +438,25 @@ class Program }); this.ZoneId = zone.Id; + this.Nameservers = zone.NameServers; + this.Status = zone.Status; } [Output] public Output ZoneId { get; set; } + public Output> Nameservers { get; set; } + public Output Status { get; set; } } } ``` -```yaml -environment: - - my-dev-env +**Filename: `Pulumi.yaml`** +```yaml +name: addsite-cloudflare +runtime: yaml resources: myZone: type: cloudflare:Zone @@ -460,178 +468,29 @@ resources: outputs: zoneId: ${myZone.id} + nameservers: ${exampleZone.nameServers} + status: ${exampleZone.status} ``` -### c. Apply the changes +Notice that the code also outputs several properties from the Zone resource, such as the `zoneId`, `nameservers`, and `status`, so that they can easily be accessed in subsequent steps. -```sh -pulumi up --yes --stack dev -# wait a few seconds while the changes take effect -``` - -### d. (Optional) Review the zone +### b. Add a DNS Record -Review the value of `zoneId` to confirm the Zone creation. +You will now add a DNS [Record resource](https://www.pulumi.com/registry/packages/cloudflare/api-docs/record/) to test previously configured Zone. -```sh -pulumi stack output zoneId -``` - -```sh output -d8fcb6d731fe1c2d75e2e8d6ad63fad5 -``` - -## Update your nameservers - -Once you have added a domain to Cloudflare, that domain will receive two assigned authoritative nameservers. - -:::note - -This process makes Cloudflare your authoritative DNS provider, allowing your DNS queries and web traffic to be served from and protected by the Cloudflare network. - -[Learn more about pending domains](/dns/zone-setups/reference/domain-status/) -::: - -### a. Update the program - -Towards the end of your entrypoint file, below the `zoneId` variable, add the following: +Add the following code snippet to your entrypoint file **after** the Zone resource definition: -```js -exports.nameservers = zone.nameServers; -exports.status = zone.status; -``` - - - -```typescript -export const nameservers = zone.nameServers; -export const status = zone.status; -``` - - - -```py -pulumi.export('nameservers', zone.name_servers) -pulumi.export('status', zone.status) -``` - - - -```go -ctx.Export("nameservers", zone.NameServers) -ctx.Export("status", zone.Status) -``` - - - -```java -ctx.export("nameservers", zone.nameServers()); -ctx.export("status", zone.status()); -``` - - - -1. Add `using System.Collections.Immutable;` at the top of your `Program.cs` file. -2. Below `this.ZoneId = zone.Id;`, add: - -```csharp -this.Nameservers = zone.NameServers; -this.Status = zone.Status; -``` - -3. Below `public Output ZoneId { get; set; }`, add: - -```csharp -public Output> Nameservers { get; set; } -public Output Status { get; set; } -``` - - - -```yaml -nameservers: ${exampleZone.nameServers} -status: ${exampleZone.status} -``` - - - -### b. Apply the changes - -```sh -pulumi up --yes --stack dev -``` - -### c. Obtain the nameservers - -Review the value of `nameservers` to retrieve the assigned nameservers: - -```sh -pulumi stack output --stack dev -``` - -### d. Update your registrar - -:::note - -If you use `example.com` as your site, skip ahead to [Add a DNS record](#add-a-dns-record). -::: - -Update the nameservers at your registrar to activate Cloudflare services for your domain. Instructions are registrar-specific. You may be able to find guidance under [this consolidated list of common registrars](/dns/zone-setups/full-setup/setup/#update-your-registrar). - -:::caution - -Registrars take up to 24 hours to process nameserver changes. -::: - -### e. Check your domain status - -Once successfully registered, your domain `status` will change to `active`. - -```sh -pulumi stack output -``` - -## Add a DNS record - -You will now add a DNS record to your domain. - -### a. Modify your program - -Below is the final version of how your Pulumi program entrypoint file should look. -Replace the contents of your entrypoint file with the following: - - +**Filename: `index.js`** ```js -"use strict"; -const pulumi = require("@pulumi/pulumi"); -const cloudflare = require("@pulumi/cloudflare"); - -const config = new pulumi.Config(); -const accountId = config.require("accountId"); -const domain = config.require("domain"); - -// Create a Cloudflare resource (Zone) -const zone = new cloudflare.Zone("my-zone", { - zone: domain, - accountId: accountId, - plan: "free", - jumpStart: true, -}); - -// Export the zone ID -exports.zoneId = zone.id; -exports.nameservers = zone.nameServers; -exports.status = zone.status; - const record = new cloudflare.Record("my-record", { zoneId: zone.id, name: domain, - value: "192.0.2.1", + content: "192.0.2.1", type: "A", proxied: true, }); @@ -639,36 +498,13 @@ const record = new cloudflare.Record("my-record", { -```typescript -import * as pulumi from "@pulumi/pulumi"; -import * as cloudflare from "@pulumi/cloudflare"; - -const config = new pulumi.Config(); -const accountId = config.require("accountId"); -const domain = config.require("domain"); - -// Create a Cloudflare resource (Zone) -const zone = new cloudflare.Zone("my-zone", { - zone: domain, - accountId: accountId, - plan: "free", // Choose the desired plan, e.g., "free", "pro", "business", etc. - jumpStart: true, -}); - -// Export the zone ID -export const zoneId = zone.id; - -// Export the Cloudflare-assigned nameservers. -export const nameservers = zone.nameServers; - -// Export the status -export const status = zone.status; +**Filename: `index.ts`** -// Set up a Record for your site +```typescript const record = new cloudflare.Record("my-record", { - zoneId: zoneId, + zoneId: zone.id, name: domain, - value: "192.0.2.1", + content: "192.0.2.1", type: "A", proxied: true, }); @@ -676,29 +512,13 @@ const record = new cloudflare.Record("my-record", { -```py -import pulumi -import pulumi_cloudflare as cloudflare - -account_id = pulumi.Config().require("accountId") -domain = pulumi.Config().require("domain") - -# Create a Cloudflare resource (Zone) -zone = cloudflare.Zone("my-zone", - zone=domain, - account_id=account_id, - plan="free", - jump_start=True) - -# Export the zone ID -pulumi.export("zoneId", zone.id) -pulumi.export('nameservers', zone.name_servers) -pulumi.export('status', zone.status) +**Filename: `__main__.py`** +```py record = cloudflare.Record("my-record", zone_id=zone.id, name=domain, - value="192.0.2.1", + content="192.0.2.1", type="A", proxied=True ) @@ -707,37 +527,14 @@ record = cloudflare.Record("my-record", -```go -package main - -import ( - cloudflare "github.com/pulumi/pulumi-cloudflare/sdk/v3/go/cloudflare" - "github.com/pulumi/pulumi/sdk/v3/go/pulumi" -) - -func main() { - pulumi.Run(func(ctx *pulumi.Context) error { - domain, _ := ctx.GetConfig("domain") - - // Create a Cloudflare resource (Zone) - zone, err := cloudflare.NewZone(ctx, "my-zone", &cloudflare.ZoneArgs{ - Zone: pulumi.String(domain), - Plan: pulumi.String("free"), - JumpStart: pulumi.Bool(true), - }) - if err != nil { - return err - } +**Filename: `main.go`** - // Export the zone ID - ctx.Export("zoneId", zone.ID()) - ctx.Export("nameservers", zone.NameServers) - ctx.Export("status", zone.Status) +```go _, err = cloudflare.NewRecord(ctx, "my-record", &cloudflare.RecordArgs{ ZoneId: zone.ID(), Name: pulumi.String(domain), - Value: pulumi.String("192.0.2.1"), + Content: pulumi.String("192.0.2.1"), Type: pulumi.String("A"), Proxied: pulumi.Bool(true), }) @@ -745,145 +542,114 @@ func main() { return err } - return nil - }) -} - ``` +**Filename: `src/main/java/myproject/App.java`** + ```java -package myproject; -import com.pulumi.Pulumi; -import com.pulumi.Context; -import com.pulumi.cloudflare.ZoneArgs; -import com.pulumi.cloudflare.Zone; +// Add imports import com.pulumi.cloudflare.Record; import com.pulumi.cloudflare.RecordArgs; - -public class App { - public static void main(String[] args) { - Pulumi.run(ctx -> { - var config = ctx.config(); - - String accountId = config.require("accountId"); - String domain = config.require("domain"); - - var zone = new Zone("my-zone", ZoneArgs.builder() - .zone(domain) - .accountId(accountId) - .plan("free") - .jumpStart(true) - .build()); - - ctx.export("zoneId", zone.id()); - ctx.export("nameservers", zone.nameServers()); - ctx.export("status", zone.status()); - - new Record("my-record", RecordArgs.builder() - .zoneId(zone.id()) - .name(domain) - .value("192.0.2.1") - .type("A") - .proxied(true) - .build()); - }); - } -} +// Below the Zone resource, add +new Record("my-record", RecordArgs.builder() +.zoneId(zone.id()) +.name(domain) +.content("192.0.2.1") +.type("A") +.proxied(true) +.build()); ``` -```csharp -using System.Collections.Immutable; -using System.Threading.Tasks; -using Pulumi; -using Pulumi.Cloudflare; +**Filename: `Program.cs`** -class Program +```csharp +new Record("my-record", new RecordArgs { - static Task Main() => Deployment.RunAsync(); - - class MyStack : Stack - { - public MyStack() - { - var config = new Pulumi.Config(); - var accountId = config.Require("accountId"); - var domain = config.Require("domain"); - - var zone = new Zone("my-zone", new ZoneArgs - { - ZoneName = domain, - AccountId = accountId, - Plan = "free", - JumpStart = true - }); - - this.ZoneId = zone.Id; - this.Nameservers = zone.NameServers; - this.Status = zone.Status; - - new Record("my-record", new RecordArgs - { - ZoneId = zone.Id, - Name = domain, - Value = "192.0.2.1", - Type = "A", - Proxied = true - }); - - } - - [Output] - public Output ZoneId { get; set; } - public Output> Nameservers { get; set; } - public Output Status { get; set; } - } -} + ZoneId = zone.Id, + Name = domain, + Content = "192.0.2.1", + Type = "A", + Proxied = true +}); ``` -```yaml -environment: - - my-dev-env - -resources: - myZone: - type: cloudflare:Zone - properties: - zone: ${domain} - accountId: ${accountId} - plan: "free" - jumpStart: true +**Filename: `Pulumi.yaml`** +```yaml myRecord: type: cloudflare:Record properties: zoneId: ${myZone.id} name: ${domain} - value: 192.0.2.1 + content: 192.0.2.1 type: A proxied: true -outputs: - zoneId: ${myZone.id} - nameservers: ${exampleZone.nameServers} - status: ${exampleZone.status} ``` -### b. Apply the changes +## 3. Deploy your changes + +Now that you have defined your resources, you can deploy the changes using the Pulumi CLI so that they are reflected in your Cloudflare account. + +To deploy the changes, run: ```sh -pulumi up --yes --stack dev +pulumi up --yes +``` + +```sh output +wait for the dev stack to become ready ``` -## Verify your setup +## 4. Configure your DNS provider + +:::note + +This process makes Cloudflare your authoritative DNS provider, allowing your DNS queries and web traffic to be served from and protected by the Cloudflare network. + +[Learn more about pending domains](/dns/zone-setups/reference/domain-status/) +::: + +:::note +If your site is `example.com`, skip ahead to [Test your site](#5-test-your-site). +::: + +### a. Obtain your nameservers + +Once you have added a domain to Cloudflare, that domain will receive two assigned authoritative nameservers. + +To retrieve the assigned `nameservers`, run: + +```sh +pulumi stack output +``` + +### b. Update your registrar + +Update the nameservers at your registrar to activate Cloudflare services for your domain. The instructions are registrar-specific. You may be able to find guidance under [this consolidated list of common registrars](/dns/zone-setups/full-setup/setup/#update-your-registrar). + +:::caution +Registrars take up to 24 hours to process nameserver changes. +::: + +### c. Check your domain status + +Once successfully registered, your domain `status` will change to `active`. + +```sh +pulumi stack output +``` + +## 5. Test your site You will run two `nslookup` commands against the Cloudflare-assigned nameservers. @@ -897,11 +663,15 @@ nslookup $DOMAIN $NS1 nslookup $DOMAIN $NS2 ``` -For .NET use `Nameservers` as the Output. +For .NET, use `Nameservers` as the Output. Confirm your response returns the IP address(es) for your site. -## Clean up +:::note +You will not receive a valid response if you use `example.com` as your site. +::: + +## 6. Clean up In this last step, you will remove the resources and stack used throughout the tutorial. @@ -917,8 +687,8 @@ pulumi destroy --yes pulumi stack rm dev ``` -You have incrementally defined Cloudflare resources needed to add a site to Cloudflare. After each new resource, you apply the changes to your `dev` stack via the `pulumi up` command. You declare the resources in your programming language of choice and let Pulumi handle the rest. - ## Next steps -Follow the [Hello World tutorial](/pulumi/tutorial/hello-world/) to deploy a serverless app with Pulumi. +You have incrementally defined Cloudflare resources needed to add a site to Cloudflare. You declare the resources in your programming language of choice and let Pulumi handle the rest. + +To deploy a serverless app with Pulumi, follow the [Deploy a Worker tutorial](/pulumi/tutorial/hello-world/). \ No newline at end of file diff --git a/src/content/docs/pulumi/tutorial/hello-world.mdx b/src/content/docs/pulumi/tutorial/hello-world.mdx index ed931d4c5b9686..7ec579c436dd69 100644 --- a/src/content/docs/pulumi/tutorial/hello-world.mdx +++ b/src/content/docs/pulumi/tutorial/hello-world.mdx @@ -1,13 +1,18 @@ --- -title: Deploy a Worker with Pulumi +title: Deploy a Worker pcx_content_type: tutorial products: - Workers -updated: 2024-01-08 -content_type: 📝 Tutorial +updated: 2024-09-13 difficulty: Beginner languages: + - JavaScript - TypeScript + - Python + - Go + - Java + - .NET + - YAML sidebar: order: 3 head: @@ -15,325 +20,1193 @@ head: content: Deploy a Worker --- -In this tutorial, you will go through step-by-step instructions to deploy a Hello World web application using Cloudflare Workers and Pulumi Infrastructure as Code (IaC) so that you can become familiar with the resource management lifecycle. In particular, you will create a Worker, a Route, and a DNS Record to access the application before cleaning up all the resources. +import { TabItem, Tabs } from "~/components"; -![alt_text](~/assets/images/pulumi/hello-world-tutorial/sn2.png "Running Cloudflare Workers application deployed with Pulumi") +In this tutorial, you will follow step-by-step instructions to deploy a Hello World application using Cloudflare Workers and Pulumi infrastructure as code (IaC) to familiarize yourself with the resource management lifecycle. In particular, you will create a Worker, a Route, and a DNS Record to access the application before cleaning up all the resources. :::note - You will provision resources that qualify under free tier offerings for both Pulumi Cloud and Cloudflare. - ::: ## Before you begin Ensure you have: + +- A Cloudflare account and API Token with permission to edit the resources in this tutorial. If you need to, sign up for a [Cloudflare account](https://www.cloudflare.com/sign-up) before continuing. Your token must have the following: + - `Account-Workers Scripts-Edit` permission + - `Zone-Workers Route-Edit` permission + - `Zone-DNS-Edit` permission +- A Pulumi Cloud account. You can sign up for an [always-free individual tier](https://app.pulumi.com/signup). +- The [Pulumi CLI](/pulumi/installing/) is installed on your machine. +- A [Pulumi-supported programming language](https://github.com/pulumi/pulumi?tab=readme-ov-file#languages) configured. (TypeScript, JavaScript, Python, Go, .NET, Java, or use YAML) +- A Cloudflare-managed domain. Complete the [Add a site tutorial](/pulumi/tutorial/add-site/) to bring your existing domain under Cloudflare. -- A Cloudflare account and API Token with permission to edit the resources in this tutorial. If you need to, sign up for a [Cloudflare account](https://dash.cloudflare.com/sign-up/workers-and-pages) before continuing. -- A Pulumi Cloud account. You can sign up for an [always-free, individual tier](https://app.pulumi.com/signup). -- [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) and the [Pulumi CLI](/pulumi/installing/) installed on your machine. -- A Cloudflare Zone. Complete the [Add a Site tutorial](/pulumi/tutorial/add-site/) to create one. +## 1. Initialize your project -:::note[Link to the full solution] +A Pulumi project is a collection of files in a dedicated folder that describes the infrastructure you want to create. The Pulumi project folder is identified by the required `Pulumi.yaml` file. You will use the Pulumi CLI to create and configure a new project. + +### a. Create a directory -You can find the complete solution of this tutorial under [this Pulumi repo and branch](https://github.com/pulumi/tutorials/tree/cloudflare-typescript-hello-world-end). To deploy the final version, run the following: +Use a new and empty directory for this tutorial. ```sh -mkdir serverless-cloudflare && cd serverless-cloudflare -pulumi new https://github.com/pulumi/tutorials/tree/cloudflare-typescript-hello-world-end -npm install -pulumi up --yes +mkdir serverless-cloudflare +cd serverless-cloudflare ``` -::: +### b. Login to Pulumi Cloud -## Initialize Pulumi +[Pulumi Cloud](https://www.pulumi.com/product/pulumi-cloud/) is a hosted service that provides a secure and scalable platform for managing your infrastructure as code. You will use it to store your Pulumi backend configurations. -### a. Create a directory +At the prompt, press Enter to log into your Pulumi Cloud account via the browser. Alternatively, you may provide a [Pulumi Cloud access token](https://www.pulumi.com/docs/pulumi-cloud/access-management/access-tokens/). + +```sh +pulumi login +``` + +### c. Create a new program + +A Pulumi program is code written in a [supported programming language](https://github.com/pulumi/pulumi?tab=readme-ov-file#languages) that defines infrastructure resources. -You'll use a new and empty directory for this tutorial. +To create a program, select your language of choice and run the `pulumi` command: + + ```sh -mkdir serverless-cloudflare -cd serverless-cloudflare +pulumi new javascript --name serverless-cloudflare --yes +# wait a few seconds while the project is initialized ``` -### b. Login + -At the prompt, press Enter to log into your Pulumi Cloud account via the browser. Alternatively, you may provide a [Pulumi Cloud access token](https://www.pulumi.com/docs/pulumi-cloud/access-management/access-tokens/). +```sh +pulumi new typescript --name serverless-cloudflare --yes +# wait a few seconds while the project is initialized +``` + + ```sh -pulumi login +pulumi new python --name serverless-cloudflare --yes +# wait a few seconds while the project is initialized ``` -### c. Create a program + -:::note +```sh +pulumi new go --name serverless-cloudflare --yes +# wait a few seconds while the project is initialized +``` -A Pulumi program is code written in a [supported programming language](https://www.pulumi.com/docs/languages-sdks/) that defines infrastructure resources. We'll use TypeScript. + -::: +```sh +pulumi new java --name serverless-cloudflare --yes +# wait a few seconds while the project is initialized +``` -To create a program, run: + ```sh -pulumi new https://github.com/pulumi/tutorials/tree/cloudflare-typescript-hello-world-begin +pulumi new csharp --name serverless-cloudflare --yes +# wait a few seconds while the project is initialized ``` -Complete the prompts with defaults where available; otherwise, provide the requested information. You will need: + -- Your Cloudflare [account ID](/fundamentals/setup/find-account-and-zone-ids/). -- Your Cloudflare [Zone ID](/fundamentals/setup/find-account-and-zone-ids/). -- A registered domain. For instance, `example.com` -- A valid Cloudflare API [token](/fundamentals/api/get-started/create-token/). +```sh +pulumi new yaml --name serverless-cloudflare --yes +``` + + ### d. Create a stack -:::note +A Pulumi [stack](https://www.pulumi.com/docs/concepts/stack/) is an instance of a Pulumi program. Stacks are independently configurable and may represent different environments (development, staging, production) or feature branches. For this tutorial, you'll use the `dev` stack. -A Pulumi stack is an instance of a Pulumi program. Stacks are independently configurable and may represent different environments (development, staging, production) or feature branches. +To instantiate your `dev` stack, run: -::: +```sh +pulumi up --yes +# wait a few seconds for the stack to be instantiated. +``` -To create a stack, run: +You have not defined any resources at this point, so you'll have an empty stack. + +### e. Save your application settings + +In this step, you will store your application settings in a Pulumi [ESC Environment](https://www.pulumi.com/docs/esc/environments/), a YAML file containing configurations and secrets. These can be accessed in several ways, including a Pulumi program. All ESC Environments securely reside in your Pulumi Cloud account and can be fully managed via the Pulumi CLI. For this tutorial, you will store the following values: + +- Your Cloudflare [account ID](/fundamentals/setup/find-account-and-zone-ids/). +- A valid Cloudflare API [token](/fundamentals/api/get-started/create-token/). +- A domain. For instance, `example.com`. ```sh -pulumi up --yes +# Give your new ESC Environment a name +E=hello-world/dev-env + +# Initialize the new ESC Environment +pulumi config env init --env $E --yes ``` -After the above command completes, review the value of `myFirstOutput` for correctness. +```sh output +Creating environment hello-world/dev-env for stack dev... +``` -### e. (Optional) Review the stack +```sh +# Replace abc123 with your Cloudflare account ID +pulumi env set $E --plaintext pulumiConfig.accountId abc123 -From the output above, follow **your** _View in Browser_ link to get familiar with the Pulumi stack. +# Replace API_TOKEN with your Cloudflare API token +pulumi env set $E --secret pulumiConfig.cloudflare:apiToken API_TOKEN -:::note +# Replace example.com with your domain +pulumi env set $E --plaintext pulumiConfig.domain example.com +``` -You have not yet created any Cloudflare resources but have defined a variable, `myFirstOutput`, and the Pulumi stack. +### f. Install the Cloudflare package -::: +You need to install the Cloudflare package for your language of choice in order to define Cloudflare resources in your Pulumi program. + +Install the Cloudflare package by running the following command: + + + +```sh +npm install @pulumi/cloudflare +``` + +```sh output +added 1 package ... +``` + + -Example: +```sh +npm install @pulumi/cloudflare +``` -```bash -View in Browser (Ctrl+O): -https://app.pulumi.com/diana-pulumi-corp/serverless-cloudflare/dev/updates/1 +```sh output +added 1 package ... ``` -![alt_text](~/assets/images/pulumi/hello-world-tutorial/sn3.png "Pulumi Cloud stack") + -## Add a Worker +```sh +echo "pulumi_cloudflare>=5.38,<6.0.0" >> requirements.txt +source venv/bin/activate +pip install -r requirements.txt +``` -You will now add a Cloudflare Worker to the Pulumi stack, `dev`. +```sh output +...Collecting pulumi-cloudflare... +``` -### a. Add Cloudflare Worker to index.ts + -Replace the contents of your `index.ts` file with the following: +```sh +go get github.com/pulumi/pulumi-cloudflare/sdk/v3/go/cloudflare +``` -```typescript -import * as pulumi from "@pulumi/pulumi"; -import * as cloudflare from "@pulumi/cloudflare"; -import * as fs from "fs"; +```sh output +go: downloading github.com/pulumi/pulumi-cloudflare ... +``` -const config = new pulumi.Config(); -const accountId = config.require("accountId"); + -// A Worker script to invoke -export const script = new cloudflare.WorkerScript("hello-world-script", { - accountId: accountId, - name: "hello-world", - // Read the content of the worker from a file - content: fs.readFileSync("./app/worker.ts", "utf8"), -}); +Below are Apache Maven instructions. For other Java project managers such as Gradle, see the official [Maven repository](https://central.sonatype.com/artifact/com.pulumi/cloudflare/overview) + +1. Open your `pom.xml` file. +2. Add the Pulumi Cloudflare dependency inside the `` section. + +```xml + + com.pulumi + cloudflare + 5.38.0 + ``` -### b. Install dependencies +3. Run: ```sh -npm install @pulumi/cloudflare +mvn clean install +``` + +```sh output +...[INFO] BUILD SUCCESS... ``` -### c. Apply the changes + ```sh -pulumi up --yes +dotnet add package Pulumi.Cloudflare +``` + +```sh output +... +info : Adding PackageReference for package 'Pulumi.Cloudflare' into project +... ``` -### d. (Optional) View the Cloudflare Dashboard + + +There are no dependencies to download for YAML. Skip ahead. + + + +## 2. Define Cloudflare resources in code -You can view your Cloudflare resource directly in the Cloudflare Dashboard to validate its existence. +With the Cloudflare package installed, you can now define any [supported Cloudflare resource](https://www.pulumi.com/registry/packages/cloudflare/) in your Pulumi program. Next, define a Worker, a Route, and a DNS Record. -1. Log into the [Cloudflare dashboard](https://dash.cloudflare.com/). -2. Select your account. -3. Go to **Workers & Pages**. -4. Open the "hello-world" application. Example: - ![alt_text](~/assets/images/pulumi/hello-world-tutorial/sn4.png) +### a. Add a Workers script -## Add a Worker route +The [Workers Script resource](https://www.pulumi.com/registry/packages/cloudflare/api-docs/workersscript/) represents a Cloudflare Worker that can be deployed to the Cloudflare network. -You will now add a Worker Route to the Pulumi stack, `dev` so the script can have an endpoint. +Replace the contents of your entrypoint file with the following: -### a. Add Worker Route to index.ts + -Replace the contents of your `index.ts` file with the following: +**Filename: `index.js`** + +```javascript +"use strict"; +const pulumi = require("@pulumi/pulumi"); +const cloudflare = require("@pulumi/cloudflare"); + +const config = new pulumi.Config(); +const accountId = config.require("accountId"); +const domain = config.require("domain"); + +const content = `export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response("Hello World!", options); + }, +};` + +const worker = new cloudflare.WorkersScript("hello-world-worker", { + accountId: accountId, + name: "hello-world-worker", + content: content, + module: true, // ES6 module +}); +``` + + + +**Filename: `index.ts`** ```typescript import * as pulumi from "@pulumi/pulumi"; import * as cloudflare from "@pulumi/cloudflare"; -import * as fs from "fs"; const config = new pulumi.Config(); const accountId = config.require("accountId"); -const zoneId = config.require("zoneId"); const domain = config.require("domain"); -// A Worker script to invoke -export const script = new cloudflare.WorkerScript("hello-world-script", { - accountId: accountId, - name: "hello-world", - // Read the content of the worker from a file - content: fs.readFileSync("./app/worker.ts", "utf8"), +const content = `export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response("Hello World!", options); + }, +};` + +const worker = new cloudflare.WorkersScript("hello-world-worker", { + accountId: accountId, + name: "hello-world-worker", + content: content, + module: true, // ES6 module }); +``` + + + +**Filename: `__main__.py`** + +```python +"""Pulumi program """ +import pulumi +import pulumi_cloudflare as cloudflare + +CONFIG = pulumi.Config() +ACCOUNT_ID = CONFIG.get("accountId") +DOMAIN = CONFIG.require("domain") +CONTENT = """ +export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response("Hello World!", options); + }, +}; +""" + +worker = cloudflare.WorkersScript("hello-world-worker", + account_id=ACCOUNT_ID, + name="hello-world-worker", + content=CONTENT, + module=True # ES6 module +) +``` -// A Worker route to serve requests and the Worker script -export const route = new cloudflare.WorkerRoute("hello-world-route", { - zoneId: zoneId, - pattern: "hello-world." + domain, - scriptName: script.name, + + +**Filename: `main.go`** + +```go +package main + +import ( + "github.com/pulumi/pulumi-cloudflare/sdk/v5/go/cloudflare" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi/config" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + conf := config.New(ctx, "") + accountID := conf.Get("accountId") + domain := conf.Get("domain") + content := ` + export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response("Hello World!", options); + }, + }; + ` + worker, err := cloudflare.NewWorkersScript(ctx, "hello-world-worker", &cloudflare.WorkersScriptArgs{ + AccountId: pulumi.String(accountID), + Name: pulumi.String("hello-world-worker"), + Content: pulumi.String(content), + Module: pulumi.Bool(true), // ES6 module + }) + if err != nil { + return err + } + + return nil + }) +} +``` + + + +**Filename: `src/main/java/myproject/App.java`** + +```java +package myproject; + +import com.pulumi.Pulumi; +import com.pulumi.cloudflare.WorkersScript; +import com.pulumi.cloudflare.WorkersScriptArgs; +import com.pulumi.core.Output; + +public class App { + public static void main(String[] args) { + Pulumi.run(ctx -> { + var content = """ + export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response("Hello World!", options); + }, + }; + """; + + var accountId = ctx.config().require("accountId"); + var domain = ctx.config().require("domain"); + var worker = new WorkersScript("hello-world-worker", WorkersScriptArgs.builder() + .accountId(accountId) + .name("hello-world-worker") + .content(content) + .module(true) + .build()); + + return; + }); + } +} +``` + + + +**Filename: `Program.cs`** + +```csharp +using Pulumi; +using Cloudflare = Pulumi.Cloudflare; + +return await Deployment.RunAsync(() => +{ + var config = new Config(); + var accountId = config.Require("accountId"); + var domain = config.Require("domain"); + var content = @" + export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response(""Hello World!"", options); + }, + }; + "; + + var worker = new Cloudflare.WorkersScript("hello-world-worker", new() + { + AccountId = accountId, + Name = "hello-world-worker", + Content = content, + Module = true + }); + return; }); ``` -### b. Apply changes + + +**Filename: `Pulumi.yaml`** + +```yaml +name: serverless-cloudflare +runtime: yaml +resources: + worker: + type: cloudflare:WorkersScript + properties: + accountId: "${accountId}" + name: "hello-world-worker" + content: | + export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response("Hello World!", options); + }, + }; + module: true +``` -```sh -pulumi up --yes + + +### b. Add a Route + +You will now add a [Workers Route resource](https://www.pulumi.com/registry/packages/cloudflare/api-docs/workersroute/) to your Pulumi program so the Workers script can have an endpoint and be active. To properly configure the Route, you will also look up the zone ID for your domain. + +Add the following code snippet to your entrypoint file **after** the Worker script resource: + + + +**Filename: `index.js`** + +```javascript +const zone = cloudflare.getZone({ + accountId: accountId, + name: domain +}); + +const zoneId = zone.then(z => z.zoneId); + +const route = new cloudflare.WorkersRoute("hello-world-route", { + zoneId: zoneId, + pattern: "hello-world." + domain, + scriptName: worker.name, +}); +``` + + + +**Filename: `index.ts`** + +```typescript +const zone = cloudflare.getZone({ + accountId: accountId, + name: domain +}); + +const zoneId = zone.then(z => z.zoneId); + +const route = new cloudflare.WorkersRoute("hello-world-route", { + zoneId: zoneId, + pattern: "hello-world." + domain, + scriptName: worker.name, +}); +``` + + + +**Filename: `__main__.py`** + +```python +zone = cloudflare.get_zone(account_id=ACCOUNT_ID, name=DOMAIN) +zone_id = zone.zone_id +route = cloudflare.WorkersRoute("hello-world-route", + zone_id=zone_id, + pattern="hello-world." + DOMAIN, + script_name=worker.name +) +``` + + + +**Filename: `main.go`** + +```go +zone, err := cloudflare.LookupZone(ctx, &cloudflare.LookupZoneArgs{ + AccountId: &accountID, + Name: &domain, +}, nil) +if err != nil { + return err +} + +route, err := cloudflare.NewWorkersRoute(ctx, "hello-world-route", &cloudflare.WorkersRouteArgs{ + ZoneId: pulumi.String(zone.Id), + Pattern: pulumi.String("hello-world." + domain), + ScriptName: worker.Name, +}) +if err != nil { + return err +} +``` + + + +**Filename: `src/main/java/myproject/App.java`** + +```java +final var zone = CloudflareFunctions.getZone(GetZoneArgs.builder() + .accountId(accountId) + .name(domain) + .build()); +var route = new WorkersRoute("hello-world-route", WorkersRouteArgs.builder() + .zoneId(zone.applyValue(getZoneResult -> getZoneResult.id())) + .pattern("hello-world." + domain) + .scriptName(worker.name()) + .build()); +``` + + + +**Filename: `Program.cs`** + +```csharp +var zone = Output.Create(Cloudflare.GetZone.InvokeAsync(new() +{ + AccountId = accountId, + Name = domain, +})); +var route = new Cloudflare.WorkersRoute("hello-world-route", new() +{ + ZoneId = zone.Apply(z => z.Id), + Pattern = "hello-world." + domain, + ScriptName = worker.Name, +}); ``` -### c. (Optional) View the Cloudflare Worker route in the dashboard + -In the Cloudflare Dashboard, the Worker application now contains the previously defined Worker Route. +**Filename: `Pulumi.yaml`** -1. Log into the [Cloudflare dashboard](https://dash.cloudflare.com/). -2. Select your account. -3. Go to **Workers & Pages**. -4. Select your application. -5. For **Routes**, select **View** to verify the Worker Route details match your definition. - ![alt_text](~/assets/images/pulumi/hello-world-tutorial/sn5.png "Cloudflare Dashboard - Worker Route") +Below the `runtime` key, add the following code: -## Add a DNS record +```yaml +# new top-level section +variables: + zone: + fn::invoke: + function: cloudflare:getZone + arguments: + accountId: ${accountId} + name: ${domain} +``` + +Below the `worker` resource, add the following code: + +```yaml + route: + type: cloudflare:WorkersRoute + properties: + zoneId: ${zone.id} + pattern: "hello-world.${domain}" + scriptName: ${worker.name} +``` -You will now add a DNS record to your domain so the previously configured route can be accessed via a URL. + -### a. Add DNS Record to index.ts +### c. Add a DNS Record -Replace the contents of your `index.ts` file with the following: +You will now add a DNS [Record resource](https://www.pulumi.com/registry/packages/cloudflare/api-docs/record/) to resolve the previously configured Route. In the next step, you'll also output the Route URL so it can be easily accessed. + +Add the following code snippet to your entrypoint file **after** the Route resource: + + + +**Filename: `index.js`** + +```javascript +const record = new cloudflare.Record("hello-world-record", { + name: route.pattern, + type: "A", + content: "192.0.2.1", + zoneId: zoneId, + proxied: true +}); + +exports.url = pulumi.interpolate`https://${record.hostname}` +``` + + + +**Filename: `index.ts`** ```typescript -import * as pulumi from "@pulumi/pulumi"; -import * as cloudflare from "@pulumi/cloudflare"; -import * as fs from "fs"; +const record = new cloudflare.Record("hello-world-record", { + name: route.pattern, + type: "A", + content: "192.0.2.1", + zoneId: zoneId, + proxied: true, +}); + +export const url = pulumi.interpolate`https://${record.hostname}`; +``` + + + +**Filename: `__main__.py`** + +```python +record = cloudflare.Record("hello-world-record", + name=route.pattern, + type="A", + content="192.0.2.1", + zone_id=zone_id, + proxied=True +) + +url = pulumi.Output.concat("https://", record.hostname) +pulumi.export('url', url) +``` + + + +**Filename: `main.go`** + +```go +record, err := cloudflare.NewRecord(ctx, "hello-world-record", &cloudflare.RecordArgs{ + Name: route.Pattern, + Type: pulumi.String("A"), + Content: pulumi.String("192.0.2.1"), + ZoneId: pulumi.String(zone.Id), + Proxied: pulumi.Bool(true), +}) +if err != nil { + return err +} + +ctx.Export("url", pulumi.Sprintf("https://%s", record.Hostname)) +``` + + + +**Filename: `src/main/java/myproject/App.java`** + +```java +var record = new Record("hello-world-record", RecordArgs.builder() + .name(route.pattern()) + .type("A") + .content("192.0.2.1") + .zoneId(zone.applyValue(getZoneResult -> getZoneResult.id())) + .proxied(true) + .build()); + +ctx.export("url", Output.format("https://%s", record.hostname())); +``` + + + +**Filename: `Program.cs`** + +Notice the updated ' return ' statement because you're now exporting a value. Ensure that you also include `using System.Collections.Generic;` in your imports. + +```csharp +var record = new Cloudflare.Record("hello-world-record", new() +{ + Name = route.Pattern, + Type = "A", + Content = "192.0.2.1", + ZoneId = zone.Apply(z => z.Id), + Proxied = true +}); + +return new Dictionary +{ + ["url"] = Output.Format($"https://{record.Hostname}") +}; +``` + + + +Notice the new top-level `outputs` section. + +```yaml + record: + type: cloudflare:Record + properties: + name: ${route.pattern} + type: A + content: "192.0.2.1" + zoneId: ${zone.id} + proxied: true + +outputs: + url: "https://${record.hostname}" +``` + + + +:::note + +You may need to use `http` instead depending on your domain settings. + +::: + +### d. (Optional) Verify your code + +Confirm all your changes match the full solution below: + + + + +**Filename: `index.js`** + +```javascript +"use strict"; +const pulumi = require("@pulumi/pulumi"); +const cloudflare = require("@pulumi/cloudflare"); const config = new pulumi.Config(); const accountId = config.require("accountId"); -const zoneId = config.require("zoneId"); const domain = config.require("domain"); -// A Worker script to invoke -export const script = new cloudflare.WorkerScript("hello-world-script", { - accountId: accountId, - name: "hello-world", - // Read the content of the worker from a file - content: fs.readFileSync("./app/worker.ts", "utf8"), +const content = `export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response("Hello World!", options); + }, +};` + +const worker = new cloudflare.WorkersScript("hello-world-worker", { + accountId: accountId, + name: "hello-world-worker", + content: content, + module: true, // ES6 module +}); + +const zone = cloudflare.getZone({ + accountId: accountId, + name: domain }); -// A Worker route to serve requests and the Worker script -export const route = new cloudflare.WorkerRoute("hello-world-route", { - zoneId: zoneId, - pattern: "hello-world." + domain, - scriptName: script.name, +const zoneId = zone.then(z => z.zoneId); + +const route = new cloudflare.WorkersRoute("hello-world-route", { + zoneId: zoneId, + pattern: "hello-world." + domain, + scriptName: worker.name, }); -// A DNS record to access the route from the domain -export const record = new cloudflare.Record("hello-world-record", { - zoneId: zoneId, - name: script.name, - value: "192.0.2.1", - type: "A", - proxied: true, +const record = new cloudflare.Record("hello-world-record", { + name: route.pattern, + type: "A", + content: "192.0.2.1", + zoneId: zoneId, + proxied: true }); -export const url = route.pattern; +exports.url = pulumi.interpolate`https://${record.hostname}` + ``` -:::note + + -The last line in the code will create an output with the endpoint for the Hello World app. +**Filename: `index.ts`** -::: +```typescript +import * as pulumi from "@pulumi/pulumi"; +import * as cloudflare from "@pulumi/cloudflare"; -### b. Apply the changes +const config = new pulumi.Config(); +const accountId = config.require("accountId"); +const domain = config.require("domain"); + +const content = `export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response("Hello World!", options); + }, +};` + +const worker = new cloudflare.WorkersScript("hello-world-worker", { + accountId: accountId, + name: "hello-world-worker", + content: content, + module: true, // ES6 module +}); + +const zone = cloudflare.getZone({ + accountId: accountId, + name: domain +}); + +const zoneId = zone.then(z => z.zoneId); + +const route = new cloudflare.WorkersRoute("hello-world-route", { + zoneId: zoneId, + pattern: "hello-world." + domain, + scriptName: worker.name, +}); + +const record = new cloudflare.Record("hello-world-record", { + name: route.pattern, + type: "A", + content: "192.0.2.1", + zoneId: zoneId, + proxied: true, +}); + +export const url = pulumi.interpolate`https://${record.hostname}`; -```sh -pulumi up --yes ``` -### c. (Optional) View all the resources in Pulumi Cloud + + + +**Filename: `__main__.py`** + +```python +"""Pulumi program """ +import pulumi +import pulumi_cloudflare as cloudflare + +CONFIG = pulumi.Config() +ACCOUNT_ID = CONFIG.get("accountId") +DOMAIN = CONFIG.require("domain") +CONTENT = """ +export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response("Hello World!", options); + }, +}; +""" + +worker = cloudflare.WorkersScript("hello-world-worker", + account_id=ACCOUNT_ID, + name="hello-world-worker", + content=CONTENT, + module=True # ES6 module +) + +zone = cloudflare.get_zone(account_id=ACCOUNT_ID, name=DOMAIN) +zone_id = zone.zone_id +route = cloudflare.WorkersRoute("hello-world-route", + zone_id=zone_id, + pattern="hello-world." + DOMAIN, + script_name=worker.name +) + +record = cloudflare.Record("hello-world-record", + name=route.pattern, + type="A", + content="192.0.2.1", + zone_id=zone_id, + proxied=True +) + +url = pulumi.Output.concat("https://", record.hostname) +pulumi.export('url', url) -1. In your browser, open your [Pulumi Cloud](https://app.pulumi.com/) -2. Navigate to your stack, `serverless-cloudflare/dev`. -3. Confirm all the defined resources are created and healthy. Example: +``` -![alt_text](~/assets/images/pulumi/hello-world-tutorial/sn6.png "Pulumi Cloud stack") + + + +**Filename: `main.go`** + +```go +package main + +import ( + "github.com/pulumi/pulumi-cloudflare/sdk/v5/go/cloudflare" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi/config" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + conf := config.New(ctx, "") + accountID := conf.Get("accountId") + domain := conf.Get("domain") + content := ` + export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response("Hello World!", options); + }, + }; + ` + worker, err := cloudflare.NewWorkersScript(ctx, "hello-world-worker", &cloudflare.WorkersScriptArgs{ + AccountId: pulumi.String(accountID), + Name: pulumi.String("hello-world-worker"), + Content: pulumi.String(content), + Module: pulumi.Bool(true), // ES6 module + }) + if err != nil { + return err + } + zone, err := cloudflare.LookupZone(ctx, &cloudflare.LookupZoneArgs{ + AccountId: &accountID, + Name: &domain, + }, nil) + if err != nil { + return err + } + + route, err := cloudflare.NewWorkersRoute(ctx, "hello-world-route", &cloudflare.WorkersRouteArgs{ + ZoneId: pulumi.String(zone.Id), + Pattern: pulumi.String("hello-world." + domain), + ScriptName: worker.Name, + }) + if err != nil { + return err + } + + record, err := cloudflare.NewRecord(ctx, "hello-world-record", &cloudflare.RecordArgs{ + Name: route.Pattern, + Type: pulumi.String("A"), + Content: pulumi.String("192.0.2.1"), + ZoneId: pulumi.String(zone.Id), + Proxied: pulumi.Bool(true), + }) + if err != nil { + return err + } + + ctx.Export("url", pulumi.Sprintf("https://%s", record.Hostname)) + + return nil + }) +} -## Test the app +``` -You have incrementally added all the Cloudflare resources needed to run and access your Hello World application. This was done by defining the resources in TypeScript and letting Pulumi handle the rest. + + + +**Filename: `src/main/java/myproject/App.java`** + +```java +package myproject; + +import com.pulumi.Pulumi; +import com.pulumi.core.Output; +import com.pulumi.cloudflare.WorkersScript; +import com.pulumi.cloudflare.WorkersScriptArgs; +import com.pulumi.cloudflare.CloudflareFunctions; +import com.pulumi.cloudflare.inputs.GetZoneArgs; +import com.pulumi.cloudflare.WorkersRoute; +import com.pulumi.cloudflare.WorkersRouteArgs; +import com.pulumi.cloudflare.Record; +import com.pulumi.cloudflare.RecordArgs; + +public class App { + public static void main(String[] args) { + Pulumi.run(ctx -> { + var content = """ + export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response("Hello World!", options); + }, + }; + """; + + var accountId = ctx.config().require("accountId"); + var domain = ctx.config().require("domain"); + + var worker = new WorkersScript("hello-world-worker", WorkersScriptArgs.builder() + .accountId(accountId) + .name("hello-world-worker") + .content(content) + .module(true) + .build()); + final var zone = CloudflareFunctions.getZone(GetZoneArgs.builder() + .accountId(accountId) + .name(domain) + .build()); + var route = new WorkersRoute("hello-world-route", WorkersRouteArgs.builder() + .zoneId(zone.applyValue(getZoneResult -> getZoneResult.id())) + .pattern("hello-world." + domain) + .scriptName(worker.name()) + .build()); + var record = new Record("hello-world-record", RecordArgs.builder() + .name(route.pattern()) + .type("A") + .content("192.0.2.1") + .zoneId(zone.applyValue(getZoneResult -> getZoneResult.id())) + .proxied(true) + .build()); + + ctx.export("url", Output.format("https://%s", record.hostname())); + return; + }); + } +} -You can test your application via the terminal or browser. +``` -- In the terminal + + + +**Filename: `Program.cs`** + +```csharp +using System.Collections.Generic; +using Pulumi; +using Cloudflare = Pulumi.Cloudflare; + +return await Deployment.RunAsync(() => +{ + var config = new Config(); + var accountId = config.Require("accountId"); + var domain = config.Require("domain"); + var content = @" + export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response(""Hello World!"", options); + }, + }; + "; + + var worker = new Cloudflare.WorkersScript("hello-world-worker", new() + { + AccountId = accountId, + Name = "hello-world-worker", + Content = content, + Module = true + }); + var zone = Output.Create(Cloudflare.GetZone.InvokeAsync(new() + { + AccountId = accountId, + Name = domain, + })); + var route = new Cloudflare.WorkersRoute("hello-world-route", new() + { + ZoneId = zone.Apply(z => z.Id), + Pattern = "hello-world." + domain, + ScriptName = worker.Name, + }); + + var record = new Cloudflare.Record("hello-world-record", new() + { + Name = route.Pattern, + Type = "A", + Content = "192.0.2.1", + ZoneId = zone.Apply(z => z.Id), + Proxied = true + }); + + return new Dictionary + { + ["url"] = Output.Format($"https://{record.Hostname}") + }; +}); -```sh -pulumi stack output url ``` -```sh output -hello-world.atxyall.com + + + +**Filename: `Pulumi.yaml`** + +```yaml +name: serverless-cloudflare +runtime: yaml +variables: + zone: + fn::invoke: + function: cloudflare:getZone + arguments: + accountId: ${accountId} + name: ${domain} + +resources: + worker: + type: cloudflare:WorkersScript + properties: + accountId: "${accountId}" + name: "hello-world-worker" + content: | + export default { + async fetch(request) { + const options = { headers: { 'content-type': 'text/plain' } }; + return new Response("Hello World!", options); + }, + }; + module: true + route: + type: cloudflare:WorkersRoute + properties: + zoneId: ${zone.id} + pattern: "hello-world.${domain}" + scriptName: ${worker.name} + record: + type: cloudflare:Record + properties: + name: ${route.pattern} + type: A + content: "192.0.2.1" + zoneId: ${zone.id} + proxied: true + +outputs: + url: "https://${record.hostname}" ``` + + + +## 3. Deploy your application + +Now that you have defined all the Cloudflare resources, you can deploy the Hello World application to your Cloudflare account using the Pulumi CLI. + +To deploy the changes, run: + ```sh -curl "https://$(pulumi stack output url)" +pulumi up --yes ``` ```sh output - - - - Hello World - - -

Serverless with Pulumi

-

The current time is: Thu Oct 05 2023 22:02:17 GMT+0000 (Coordinated Universal Time).

- - - +wait for the dev stack to become ready ``` -:::note - -Depending on your domain settings, you may need to use "http" instead. +## 4. Test the Worker -::: +You incrementally added Cloudflare resources to run and access your Hello World application. You can test your application by curling the `url` output from the Pulumi stack. -- In your browser, open `hello-world.YOUR_DOMAIN.com` - -Example: +```sh +curl $(pulumi stack output url) +``` -![alt_text](~/assets/images/pulumi/hello-world-tutorial/sn2.png "Hello World app browser screenshot") +```sh output +Hello, World! +``` -## Clean up +## 5. Clean up -In this last step, you will run a couple of commands to clean up the resources and stack you used throughout the tutorial. +In this last step, you will clean up the resources and stack used throughout the tutorial. ### a. Delete the Cloudflare resources @@ -346,3 +1219,7 @@ pulumi destroy ```sh pulumi stack rm dev ``` + +## Next steps + +Visit the [Cloudflare package documentation](https://www.pulumi.com/docs/reference/pkg/cloudflare/) to explore other resources you can define with Pulumi and Cloudflare. diff --git a/src/content/docs/pulumi/tutorial/manage-secrets.mdx b/src/content/docs/pulumi/tutorial/manage-secrets.mdx index 13fcb676fbe8ba..b0f4758d35b276 100644 --- a/src/content/docs/pulumi/tutorial/manage-secrets.mdx +++ b/src/content/docs/pulumi/tutorial/manage-secrets.mdx @@ -22,12 +22,12 @@ You will provision resources that qualify under free tier offerings for both Pul Ensure you have: -- A Cloudflare account. [Sign up for a Cloudflare account](https://dash.cloudflare.com/sign-up/workers-and-pages). +- A Cloudflare account. [Sign up for a Cloudflare account](https://www.cloudflare.com/sign-up). - A Pulumi Cloud account. [Sign up for a Pulumi Cloud](https://app.pulumi.com/signup). - The [Pulumi ESC CLI](https://www.pulumi.com/docs/install/esc/) installed. - A Wrangler project. To create one, follow the [Create a New Worker project step](/workers/get-started/guide/#1-create-a-new-worker-project). -## Set up a new Environment +## 1. Set up a new Environment A [Pulumi ESC Environment](https://www.pulumi.com/docs/esc/environments/), or Environment, is a YAML file containing configurations and secrets for your application and infrastructure. These can be accessed in several ways, including shell commands. All ESC Environments reside in your Pulumi Cloud account. @@ -50,7 +50,7 @@ Environment names must be unique within a Pulumi organization and may only conta ::: ```sh -ESC_ENV=my-dev-environment +ESC_ENV=wrangler/my-dev-environment esc env init $ESC_ENV ``` @@ -58,7 +58,7 @@ esc env init $ESC_ENV Environment created. ``` -## Log into Cloudflare +## 2. Log into Cloudflare Now that the Pulumi ESC Environment has been created, it can be consumed in various ways. For instance, to log into your Cloudflare account without needing to predefine credentials in your shell. @@ -80,7 +80,7 @@ esc env set $ESC_ENV environmentVariables.CLOUDFLARE_API_TOKEN 123abc --secret The API token is declared as a `secret`. Once the Environment is saved, Pulumi will encrypt its value and replace it with ciphertext. ::: -### a. Log out +### b. Log out Ensure you're not currently logged in to your Cloudflare account. @@ -92,7 +92,7 @@ npx wrangler logout Not logged in, exiting... ``` -### a. Log in +### c. Log in Pass ESC-stored Cloudflare credentials to Wrangler. @@ -107,7 +107,7 @@ Getting User settings... When you use the `esc run` command, it opens the Environment and sets the specified Environment variables into a temporary environment. After that, it uses those variables in the context of the `wrangler` command. This is especially helpful when running `wrangler` commands in a CI/CD environment but wanting to avoid storing credentials directly in your pipeline. -## Add Worker secrets +## 3. Add Worker secrets Pulumi ESC centralizes secrets, and Wrangler can be used to pass them on to Workers and other Cloudflare resources. You will use the `wrangler secret put` command for this purpose. @@ -125,7 +125,7 @@ esc run -i ${ESC_ENV} -- sh -c 'echo "$TOP_SECRET" | npx wrangler secret put TOP By using an external secrets management solution, commonly used Worker secrets can be stored in a single shared Environment that is accessed by the relevant Workers. You can use shell commands with `esc` to incorporate scripting and integrate them into deployment pipelines or `make` commands. Use `esc [command] --help` for more information about the various commands available in the CLI. -## Load `.dev.vars` +## 4. Load `.dev.vars` In this step, you will configure an Environment to load your `.dev.vars` file programmatically. @@ -138,7 +138,7 @@ With a dedicated ESC Environment to store all the `.dev.vars` secrets, you can u ### a. Create an Environment ```sh -E=my-devvars +E=wrangler/my-devvars esc env init $E ```