diff --git a/content/docs/iac/get-started/aws/_index.md b/content/docs/iac/get-started/aws/_index.md index 679010f71d8d..7154370bbb8c 100644 --- a/content/docs/iac/get-started/aws/_index.md +++ b/content/docs/iac/get-started/aws/_index.md @@ -31,36 +31,36 @@ First, choose your language and ensure you've performed any prerequisites: {{% choosable language "typescript" %}} -* An AWS account -* Node.js and npm installed locally +* An AWS account +* Node.js and npm installed locally {{% /choosable %}} {{% choosable language "python" %}} * An AWS account -* Python and pip, Poetry or uv installed locally +* Python and pip, Poetry or uv installed locally {{% /choosable %}} {{% choosable language "go" %}} * An AWS account -* Go installed locally +* Go installed locally {{% /choosable %}} {{% choosable language "csharp" %}} * An AWS account -* .NET installed locally +* .NET installed locally {{% /choosable %}} {{% choosable language "java" %}} * An AWS account -* Java 11+ and Maven 3.6.1+ installed locally +* Java 11+ and Maven 3.6.1+ installed locally {{% /choosable %}} diff --git a/content/docs/iac/get-started/aws/begin.md b/content/docs/iac/get-started/aws/begin.md index 12bf1eec7ec9..bd81f19131f9 100644 --- a/content/docs/iac/get-started/aws/begin.md +++ b/content/docs/iac/get-started/aws/begin.md @@ -24,14 +24,6 @@ aliases: - /docs/clouds/aws/get-started/begin/ --- -## Install Pulumi - -Download and install Pulumi for your platform: - -{{< install-pulumi >}} -{{% notes info %}} -All Windows examples in this tutorial assume you are running in PowerShell. -{{% /notes %}} -{{< /install-pulumi >}} +{{< get-started-install-body >}} {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/aws/configure.md b/content/docs/iac/get-started/aws/configure.md index b09ef5a48f9b..10302110c5b5 100644 --- a/content/docs/iac/get-started/aws/configure.md +++ b/content/docs/iac/get-started/aws/configure.md @@ -9,7 +9,7 @@ menu: name: Configure access parent: aws-get-started weight: 3 - + identifier: aws-get-started.configure aliases: - /docs/iac/get-started/aws/b/configure/ --- @@ -42,7 +42,7 @@ $ aws sts get-caller-identity {{% /choosable %}} -If your AWS user ID, account, and ARN are printed, you are good to go. If not, read on: +If your AWS user ID, account, and ARN are printed, your configuration is correct. If not, read on: ``` { diff --git a/content/docs/iac/get-started/aws/create-component.md b/content/docs/iac/get-started/aws/create-component.md index 59bbb6958321..eb96cc782abd 100644 --- a/content/docs/iac/get-started/aws/create-component.md +++ b/content/docs/iac/get-started/aws/create-component.md @@ -257,7 +257,7 @@ Next, make four changes: 3. Generalize the creation of bucket objects by looping over the list of `files` 4. Assign the resulting website URL to the `url` property of the component -The resulting {{< compfile >}} file will look like this; feel free to make each edit one at a time if you'd like +The resulting {{< compfile >}} file will look like this; you can make each edit one at a time if preferred to get a feel for things, or simply paste the contents of this into {{< compfile >}}: {{% choosable language typescript %}} @@ -552,7 +552,7 @@ public class AwsS3Website : Pulumi.ComponentResource }); // Create an S3 Bucket object for each file; note the changes to name/source: - foreach (var file in args.Files) { + foreach (var file in args.Files ?? []) { new BucketObject(file, new() { Bucket = bucket.Id, @@ -753,7 +753,7 @@ using Pulumi; using Pulumi.Aws.S3; using System.Collections.Generic; -return await Deployment.RunAsync(() => +return await Pulumi.Deployment.RunAsync(() => { // Create an instance of our component with the same files as before: var website = new AwsS3Website("my-website", new AwsS3WebsiteArgs() diff --git a/content/docs/iac/get-started/aws/create-project.md b/content/docs/iac/get-started/aws/create-project.md index d3f692d7a19a..15091e71c99a 100644 --- a/content/docs/iac/get-started/aws/create-project.md +++ b/content/docs/iac/get-started/aws/create-project.md @@ -218,6 +218,7 @@ If you list the contents of your directory, you'll see some key files: {{% choosable language "typescript,python,go,csharp,java" %}} - {{< langfile >}} contains your project's main code that declares a new S3 bucket + - `Pulumi.yaml` is a [project file](/docs/iac/concepts/projects/project-file) containing metadata about your project like its name {{% /choosable %}} @@ -296,7 +297,7 @@ using Pulumi; using Pulumi.Aws.S3; using System.Collections.Generic; -return await Deployment.RunAsync(() => +return await Pulumi.Deployment.RunAsync(() => { // Create an AWS resource (S3 Bucket) var bucket = new Bucket("my-bucket"); diff --git a/content/docs/iac/get-started/aws/deploy-stack.md b/content/docs/iac/get-started/aws/deploy-stack.md index 1d50e39ca9ab..d33dfb68d7b4 100644 --- a/content/docs/iac/get-started/aws/deploy-stack.md +++ b/content/docs/iac/get-started/aws/deploy-stack.md @@ -129,9 +129,7 @@ $ aws s3 ls ("s3://" + (pulumi stack output bucket_name)) ### View your update on Pulumi Cloud -If you are logged into [Pulumi Cloud](/docs/pulumi-cloud), you'll see "View Live" hyperlinks in the CLI output during your -update. These go to [a page](https://app.pulumi.com) with detailed information about your stack including resources, -configuration, a full history of updates, and more. Click on it to check it out: +If you are logged into [Pulumi Cloud](/docs/pulumi-cloud), you'll see "View Live" hyperlinks in the CLI output during your update. These go to [a page](https://app.pulumi.com) with detailed information about your stack including resources, configuration, a full history of updates, and more. Navigate to it to review the details of your update: A stack update with console output, as shown in the Pulumi Service diff --git a/content/docs/iac/get-started/aws/modify-program.md b/content/docs/iac/get-started/aws/modify-program.md index 3d24782ae415..595401497ad6 100644 --- a/content/docs/iac/get-started/aws/modify-program.md +++ b/content/docs/iac/get-started/aws/modify-program.md @@ -570,6 +570,6 @@ This will reveal your new website! Feel free to experiment, such as changing the contents of `index.html` and redeploying. -Next, let's wrap this website up into an infrastructure abstraction. +Next, wrap the website into an infrastructure abstraction. {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/azure/_index.md b/content/docs/iac/get-started/azure/_index.md index 6bacb90e76b3..6a52045bbb14 100644 --- a/content/docs/iac/get-started/azure/_index.md +++ b/content/docs/iac/get-started/azure/_index.md @@ -1,8 +1,8 @@ --- -title_tag: Get Started with Azure -meta_desc: This page provides an overview and guide on how to get started with Azure. title: Azure -h1: Get started with Pulumi & Azure +title_tag: Get started with Pulumi and Azure +h1: Get started with Pulumi and Azure +meta_desc: This page provides an overview and guide on how to get started with Azure. menu: iac: name: Azure @@ -17,8 +17,55 @@ aliases: - /docs/get-started/azure/ - /docs/quickstart/azure/ - /docs/clouds/azure/get-started/ + - /docs/iac/get-started/azure/deploy-changes/ + - /docs/iac/get-started/azure/review-project/ --- -{{< cloud-intro "Microsoft Azure" >}} +**Infrastructure as code (IaC)** lets you deploy, change, and manage infrastructure safely, consistently, +and repeatably using code rather than a graphical user interface. + +Complete this step-by-step tutorial to deploy an Azure Blob Storage-based website using IaC. + +## Before you begin + +Make sure you have the Azure CLI installed and signed in to the Azure subscription you plan to use (for example, `az login`). Then choose your language and ensure you've performed any prerequisites: + +{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} + +{{% choosable language "typescript" %}} + +* Node.js and npm installed locally + +{{% /choosable %}} + +{{% choosable language "python" %}} + +* Python and pip, Poetry or uv installed locally + +{{% /choosable %}} + +{{% choosable language "go" %}} + +* Go installed locally + +{{% /choosable %}} + +{{% choosable language "csharp" %}} + +* .NET installed locally + +{{% /choosable %}} + +{{% choosable language "java" %}} + +* Java 11+ and Maven 3.6.1+ installed locally + +{{% /choosable %}} + +{{% choosable language "yaml" %}} + +* A text editor + +{{% /choosable %}} {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/azure/begin.md b/content/docs/iac/get-started/azure/begin.md index 2c502f059cef..40b8a1c506c8 100644 --- a/content/docs/iac/get-started/azure/begin.md +++ b/content/docs/iac/get-started/azure/begin.md @@ -1,8 +1,9 @@ --- -title_tag: Before You Begin | Azure +title_tag: Install Pulumi | Azure meta_desc: This page provides an overview on how to get started with Pulumi when starting an Azure project. -title: Before you begin -h1: "Pulumi & Azure: Before you begin" +title: Install Pulumi +h1: "Get started with Pulumi and Azure" +stepper_link: "I'm ready to begin" weight: 2 menu: iac: @@ -14,78 +15,9 @@ aliases: - /docs/quickstart/azure/begin/ - /docs/quickstart/azure/install-pulumi/ - /docs/quickstart/azure/install-language-runtime/ - - /docs/quickstart/azure/configure/ - /docs/clouds/azure/get-started/begin/ --- -Before you get started using Pulumi, let's run through a few quick steps to ensure your environment is set up correctly. - -### Install Pulumi - -{{< install-pulumi >}} -{{% notes "info" %}} -All Windows examples in this tutorial assume you are running in PowerShell. -{{% /notes %}} -{{< /install-pulumi >}} - -Next, install the required language runtime, if you have not already. - -### Install Language Runtime - -#### Choose Your Language - -{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} - -{{% choosable language "typescript" %}} -{{< install-node >}} -{{% /choosable %}} - -{{% choosable language python %}} -{{< install-python >}} -{{% /choosable %}} - -{{% choosable language go %}} -{{< install-go >}} -{{% /choosable %}} - -{{% choosable language "csharp,fsharp,visualbasic" %}} -{{< install-dotnet >}} -{{% /choosable %}} - -{{% choosable language java %}} -{{< install-java >}} -{{% /choosable %}} - -{{% choosable language yaml %}} -{{< install-yaml >}} -{{% /choosable %}} - -Finally, configure Pulumi with Microsoft Azure. - -### Configure Pulumi to access your Microsoft Azure account - -Pulumi requires cloud credentials to manage and provision resources. Pulumi can authenticate to Azure using a user account or service principal that has **Programmatic access** with rights to deploy and manage your Azure resources. - -{{% notes type="info" %}} -Pulumi relies on the Azure SDK to authenticate requests from your computer to Azure. Your credentials are never sent to pulumi.com. -{{% /notes %}} - -In this guide, you will need a user account with permissions to create and populate Blob storage containers and provide anonymous access to a Blob file. - -When developing locally, we recommend that you install the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) and then authorize access with a user account. - -```bash -az login -``` - -After successfully logging in, you are ready to go. - -{{% notes type="info" %}} -The Azure CLI, and thus Pulumi, will use the default subscription for the account. You can change the active subscription with the [`az account set`](https://docs.microsoft.com/en-us/cli/azure/account?view=azure-cli-latest#az_account_set) command. -{{% /notes %}} - -For additional information on authenticating with Azure, or to login with a service principal, see [Azure Setup](/registry/packages/azure-native/installation-configuration/). - -Next, you'll create a new Pulumi project. +{{< get-started-install-body >}} {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/azure/configure.md b/content/docs/iac/get-started/azure/configure.md new file mode 100644 index 000000000000..af893593cc52 --- /dev/null +++ b/content/docs/iac/get-started/azure/configure.md @@ -0,0 +1,96 @@ +--- +title_tag: Configure access | Azure +title: Configure access +h1: "Get started with Pulumi and Azure" +meta_desc: This page provides an overview on how to get started with Pulumi when starting an Azure project. +weight: 3 +menu: + iac: + name: Configure access + parent: azure-get-started + weight: 3 + identifier: azure-get-started.configure +aliases: + - /docs/quickstart/azure/configure/ + - /docs/clouds/azure/get-started/configure/ +--- + +## Configure access to Azure + +Pulumi's CLI needs access to your Azure account to manage cloud resources. + +If you've already installed and configured the Azure CLI, Pulumi will respect and use your configuration settings. + +You must use an Azure account that has rights to deploy and manage resources, such as storage accounts and blob containers. + +### Testing access + +To test that your Azure access is configured properly, run: + +{{% choosable os "linux,macos" %}} + +```bash +$ az account show +``` + +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> az account show +``` + +{{% /choosable %}} + +If your Azure subscription details are printed, your configuration is correct. If not, read on: + +```json +{ + "environmentName": "AzureCloud", + "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "isDefault": true, + "name": "My Subscription", + "state": "Enabled", + "tenantId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "user": { + "name": "user@example.com", + "type": "user" + } +} +``` + +### Alternative approaches + +If you don't have the Azure CLI installed, or you plan on using Pulumi in a CI/CD pipeline, you can create a service principal and set the following environment variables on your workstation: + +{{% choosable os "linux,macos" %}} + +```bash +$ export ARM_CLIENT_ID="" +$ export ARM_CLIENT_SECRET="" +$ export ARM_TENANT_ID="" +$ export ARM_SUBSCRIPTION_ID="" +``` + +{{% /choosable %}} + +{{% choosable os windows %}} + +```powershell +> $env:ARM_CLIENT_ID = "" +> $env:ARM_CLIENT_SECRET = "" +> $env:ARM_TENANT_ID = "" +> $env:ARM_SUBSCRIPTION_ID = "" +``` + +{{% /choosable %}} + +{{% notes type="info" %}} +Consider using [Pulumi ESC's Azure login support](/docs/esc/integrations/dynamic-login-credentials/azure-login) for dynamic, +short-lived Azure credentials via OpenID Connect (OIDC) instead of long-lived static credentials. This is a security best practice. +{{% /notes %}} + +For detailed information on Pulumi's use of Azure credentials, see [Azure Setup](/registry/packages/azure-native/installation-configuration/). + +{{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/azure/create-component.md b/content/docs/iac/get-started/azure/create-component.md new file mode 100644 index 000000000000..1f1e312e4903 --- /dev/null +++ b/content/docs/iac/get-started/azure/create-component.md @@ -0,0 +1,867 @@ +--- +title_tag: Create a component | Azure +title: Create a component +h1: "Get started with Pulumi and Azure" +meta_desc: This page provides an overview on how to create infrastructure abstractions with Pulumi. +weight: 7 +menu: + iac: + name: Create a component + identifier: azure-get-started.create-component + parent: azure-get-started + weight: 7 + +aliases: + - /docs/quickstart/azure/create-component/ + - /docs/clouds/azure/get-started/create-component/ +--- + +## Create a component + +[**Components**](/docs/iac/concepts/resources/components/) are infrastructure abstractions that encapsulate +complexity and enable sharing and reuse. Instead of copy-pasting common patterns, you can encode them as components. + +You will now create your first component that packages up your Azure static website so you can easily stamp out +entire websites in just a few lines of code: + +{{% choosable language typescript %}} + +```typescript +const website = new AzureStaticWebsite("my-website", { + files: [ "index.html" ], +}); +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +website = AzureStaticWebsite('my-website', files=['index.html']) +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +website, err := NewAzureStaticWebsite(ctx, "my-website", AzureStaticWebsiteArgs{ + Files: []string{"index.html"}, +}) +if err != nil { + return err +} +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +var website = new AzureStaticWebsite("my-website", new AzureStaticWebsiteArgs() +{ + Files = new[] { "index.html" } +}); +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +var website = new AzureStaticWebsite("my-website", + new AzureStaticWebsiteArgs(new String[] { "index.html" })); +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +{{% notes type="warning" %}} + +Unfortunately, YAML lacks the language facilities to author components. Feel free to [skip ahead](/docs/iac/get-started/azure/destroy-stack/). + +{{% /notes %}} + +{{% /choosable %}} + +Using components here also has the benefit that, as the requirements for Azure static websites changes, you can +update the one component definition and have all uses of it benefit. + +### Define a new component + +To define a new component, create a class called `AzureStaticWebsite` that derives from `ComponentResource`. It'll have a mostly-empty +constructor to start with but you will add the Azure resources to it in the next step. You'll also define the inputs for the +component -- the `files` to add to the website -- and outputs -- a single property with the website `url`. + +To get going, create a new file {{< compfile >}} alongside {{< langfile >}} and add the following: + +{{% choosable language typescript %}} + +```typescript +import * as azure from "@pulumi/azure-native"; +import * as pulumi from "@pulumi/pulumi"; + +// Arguments for the Azure hosted static website component. +export interface AzureStaticWebsiteArgs { + files: string[]; // a list of files to serve. +} + +// A component that encapsulates creating an Azure hosted static website. +export class AzureStaticWebsite extends pulumi.ComponentResource { + public readonly url: pulumi.Output; // the website url. + + constructor(name: string, args: AzureStaticWebsiteArgs, opts?: pulumi.ComponentResourceOptions) { + super("quickstart:index:AzureStaticWebsite", name, args, opts); + + // Component initialization will go here next... + + this.registerOutputs({}); // Signal component completion. + } +} +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +import pulumi +from pulumi_azure_native import storage, resources +from typing import List + +# A component that encapsulates creating an Azure hosted static website. +class AzureStaticWebsite(pulumi.ComponentResource): + def __init__(self, name: str, files: List[str] = None, opts = None): + super().__init__('quickstart:index:AzureStaticWebsite', name, { 'files': files }, opts) + + # Component initialization will go here next... + + self.register_outputs({}) # Signal component completion. +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +package main + +import ( + "github.com/pulumi/pulumi-azure-native-sdk/resources/v2" + "github.com/pulumi/pulumi-azure-native-sdk/storage/v2" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type AzureStaticWebsite struct { + pulumi.ResourceState + Url pulumi.StringOutput // the website url. +} + +type AzureStaticWebsiteArgs struct { + Files []string // a list of files to serve. +} + +func NewAzureStaticWebsite(ctx *pulumi.Context, name string, args AzureStaticWebsiteArgs, opts ...pulumi.ResourceOption) (*AzureStaticWebsite, error) { + self := &AzureStaticWebsite{} + err := ctx.RegisterComponentResource("quickstart:index:AzureStaticWebsite", name, self, opts...) + if err != nil { + return nil, err + } + + // Component initialization will go here next... + + ctx.RegisterResourceOutputs(self, pulumi.Map{}) // Signal component completion. + return self, nil +} +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +using Pulumi; +using Pulumi.AzureNative.Resources; +using Pulumi.AzureNative.Storage; +using Pulumi.AzureNative.Storage.Inputs; +using System.Collections.Generic; + +public class AzureStaticWebsiteArgs +{ + public string[]? Files { get; set; } +} + +public class AzureStaticWebsite : Pulumi.ComponentResource +{ + public Output Url { get; private set; } + + public AzureStaticWebsite(string name, AzureStaticWebsiteArgs args, ComponentResourceOptions? opts = null) + : base("quickstart:index:AzureStaticWebsite", name, opts) + { + // Component initialization will go here next... + + this.RegisterOutputs(new Dictionary{}); // Signal component completion. + } +} +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +package myproject; + +import com.pulumi.Pulumi; +import com.pulumi.azurenative.resources.ResourceGroup; +import com.pulumi.azurenative.storage.StorageAccount; +import com.pulumi.resources.ComponentResource; +import com.pulumi.resources.ComponentResourceOptions; + +public class AzureStaticWebsiteArgs { + public String[] files; + public AzureStaticWebsiteArgs(String[] files) { + this.files = files; + } +} + +public class AzureStaticWebsite extends ComponentResource { + public Output url; + + public AzureStaticWebsite(String name, AzureStaticWebsiteArgs args, ComponentResourceOptions opts) { + super("quickstart:index:AzureStaticWebsite", name, args, opts); + + // Component initialization will go here next... + + this.registerOutputs(Map.of()); + } +} +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +{{% notes type="warning" %}} + +Unfortunately, YAML lacks the language facilities to author components. Feel free to [skip ahead](/docs/iac/get-started/azure/destroy-stack/). + +{{% /notes %}} + +{{% /choosable %}} + +This defines a component but it doesn't do much yet. + +### Refactor your code into the component + +Next, make four changes: + +1. Move all resources from {{< langfile >}} into the component's constructor +1. Change each resource to use the component [as the `parent`](/docs/iac/concepts/options/parent/) +1. Generalize the creation of blobs by looping over the list of `files` +1. Assign the resulting website URL to the `url` property of the component + +The resulting {{< compfile >}} file will look like this; you can make each edit one at a time if preferred +to get a feel for things, or simply paste the contents of this into {{< compfile >}}: + +{{% choosable language typescript %}} + +```typescript +import * as azure from "@pulumi/azure-native"; +import * as pulumi from "@pulumi/pulumi"; + +// Arguments for the Azure hosted static website component. +export interface AzureStaticWebsiteArgs { + files: string[]; // a list of files to serve. +} + +// A component that encapsulates creating an Azure hosted static website. +export class AzureStaticWebsite extends pulumi.ComponentResource { + public readonly url: pulumi.Output; // the website url. + + constructor(name: string, args: AzureStaticWebsiteArgs, opts?: pulumi.ComponentResourceOptions) { + super("quickstart:index:AzureStaticWebsite", name, args, opts); + + // Create a resource group + const resourceGroup = new azure.resources.ResourceGroup("my-group", {}, { + // Set the parent to the component (step #2) above. + // Also, do the same for all other resources below. + parent: this, + }); + + // Create a storage account + const storageAccount = new azure.storage.StorageAccount("myaccount", { + resourceGroupName: resourceGroup.name, + kind: azure.storage.Kind.StorageV2, + sku: { + name: azure.storage.SkuName.Standard_LRS, + }, + }, { parent: this }); + + // Enable static website support + const staticWebsite = new azure.storage.StorageAccountStaticWebsite("static-website", { + accountName: storageAccount.name, + resourceGroupName: resourceGroup.name, + indexDocument: "index.html", + }, { parent: this }); + + // Upload each file as a blob: + for (const file of args.files) { + new azure.storage.Blob(file, { + accountName: storageAccount.name, + containerName: staticWebsite.containerName, + resourceGroupName: resourceGroup.name, + source: new pulumi.asset.FileAsset(file), + contentType: "text/html", + }, { parent: this }); + } + + // Capture the URL and make it available as a component property and output: + this.url = storageAccount.primaryEndpoints.apply(pe => pe.web); + this.registerOutputs({ url: this.url }); // Signal component completion. + } +} +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +import pulumi +from pulumi_azure_native import storage, resources +from typing import List + +# A component that encapsulates creating an Azure hosted static website. +class AzureStaticWebsite(pulumi.ComponentResource): + def __init__(self, name: str, files: List[str] = None, opts = None): + super().__init__('quickstart:index:AzureStaticWebsite', name, { 'files': files }, opts) + + # Create a resource group + resource_group = resources.ResourceGroup('my-group', + # Set the parent to the component (step #2) above. + # Also, do the same for all other resources below. + opts=pulumi.ResourceOptions(parent=self), + ) + + # Create a storage account + storage_account = storage.StorageAccount('myaccount', + resource_group_name=resource_group.name, + kind=storage.Kind.STORAGE_V2, + sku={ + 'name': storage.SkuName.STANDARD_LRS, + }, + opts=pulumi.ResourceOptions(parent=self), + ) + + # Enable static website support + static_website = storage.StorageAccountStaticWebsite('static-website', + account_name=storage_account.name, + resource_group_name=resource_group.name, + index_document='index.html', + opts=pulumi.ResourceOptions(parent=self), + ) + + # Upload each file as a blob: + for file in files: + storage.Blob( + file, + account_name=storage_account.name, + container_name=static_website.container_name, + resource_group_name=resource_group.name, + source=pulumi.FileAsset(file), + content_type='text/html', + opts=pulumi.ResourceOptions(parent=self), + ) + + # Capture the URL and make it available as a component property and output: + self.url = storage_account.primary_endpoints.apply(lambda pe: pe.web) + self.register_outputs({ 'url': self.url }) # Signal component completion. +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +package main + +import ( + "github.com/pulumi/pulumi-azure-native-sdk/resources/v2" + "github.com/pulumi/pulumi-azure-native-sdk/storage/v2" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type AzureStaticWebsite struct { + pulumi.ResourceState + Url pulumi.StringOutput // the website url. +} + +type AzureStaticWebsiteArgs struct { + Files []string // a list of files to serve. +} + +func NewAzureStaticWebsite(ctx *pulumi.Context, name string, args AzureStaticWebsiteArgs, opts ...pulumi.ResourceOption) (*AzureStaticWebsite, error) { + self := &AzureStaticWebsite{} + err := ctx.RegisterComponentResource("quickstart:index:AzureStaticWebsite", name, self, opts...) + if err != nil { + return nil, err + } + + // Create a resource group + resourceGroup, err := resources.NewResourceGroup(ctx, "my-group", nil, + // Set the parent to the component (step #2) above. + // Also, do the same for all other resources below. + pulumi.Parent(self)) + if err != nil { + return nil, err + } + + // Create a storage account + storageAccount, err := storage.NewStorageAccount(ctx, "myaccount", &storage.StorageAccountArgs{ + ResourceGroupName: resourceGroup.Name, + Kind: pulumi.String("StorageV2"), + Sku: &storage.SkuArgs{ + Name: pulumi.String("Standard_LRS"), + }, + }, pulumi.Parent(self)) + if err != nil { + return nil, err + } + + // Enable static website support + staticWebsite, err := storage.NewStorageAccountStaticWebsite(ctx, "static-website", &storage.StorageAccountStaticWebsiteArgs{ + AccountName: storageAccount.Name, + ResourceGroupName: resourceGroup.Name, + IndexDocument: pulumi.String("index.html"), + }, pulumi.Parent(self)) + if err != nil { + return nil, err + } + + // Upload each file as a blob: + for _, file := range args.Files { + _, err = storage.NewBlob(ctx, file, &storage.BlobArgs{ + AccountName: storageAccount.Name, + ContainerName: staticWebsite.ContainerName, + ResourceGroupName: resourceGroup.Name, + Source: pulumi.NewFileAsset(file), + ContentType: pulumi.String("text/html"), + }, pulumi.Parent(self)) + if err != nil { + return nil, err + } + } + + // Capture the URL and make it available as a component property and output: + self.Url = storageAccount.PrimaryEndpoints.ApplyT(func(pe storage.EndpointsResponse) string { + return *pe.Web + }).(pulumi.StringOutput) + + ctx.RegisterResourceOutputs(self, pulumi.Map{"url": self.Url}) // Signal component completion. + return self, nil +} +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +using Pulumi; +using Pulumi.AzureNative.Resources; +using Pulumi.AzureNative.Storage; +using Pulumi.AzureNative.Storage.Inputs; +using System.Collections.Generic; + +public class AzureStaticWebsiteArgs +{ + public string[]? Files { get; set; } +} + +public class AzureStaticWebsite : Pulumi.ComponentResource +{ + public Output Url { get; private set; } + + public AzureStaticWebsite(string name, AzureStaticWebsiteArgs args, ComponentResourceOptions? opts = null) + : base("quickstart:index:AzureStaticWebsite", name, opts) + { + // Create a resource group + var resourceGroup = new ResourceGroup("my-group", new(), new CustomResourceOptions + { + // Set the parent to the component (step #2) above. + // Also, do the same for all other resources below. + Parent = this, + }); + + // Create a storage account + var storageAccount = new StorageAccount("myaccount", new() + { + ResourceGroupName = resourceGroup.Name, + Kind = Kind.StorageV2, + Sku = new SkuArgs + { + Name = SkuName.Standard_LRS, + }, + }, new CustomResourceOptions + { + Parent = this, + }); + + // Enable static website support + var staticWebsite = new StorageAccountStaticWebsite("static-website", new() + { + AccountName = storageAccount.Name, + ResourceGroupName = resourceGroup.Name, + IndexDocument = "index.html", + }, new CustomResourceOptions + { + Parent = this, + }); + + // Upload each file as a blob: + foreach (var file in args.Files ?? []) { + new Blob(file, new() + { + AccountName = storageAccount.Name, + ContainerName = staticWebsite.ContainerName, + ResourceGroupName = resourceGroup.Name, + Source = new FileAsset(file), + ContentType = "text/html", + }, new CustomResourceOptions + { + Parent = this, + }); + } + + // Capture the URL and make it available as a component property and output: + this.Url = storageAccount.PrimaryEndpoints.Apply(pe => pe.Web); + this.RegisterOutputs(new Dictionary{ + ["url"] = this.Url + }); + } +} +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +package myproject; + +import com.pulumi.*; +import com.pulumi.core.*; +import com.pulumi.asset.FileAsset; +import com.pulumi.resources.*; + +import com.pulumi.azurenative.resources.*; +import com.pulumi.azurenative.storage.*; +import com.pulumi.azurenative.storage.inputs.*; + +import java.util.Map; + +class AzureStaticWebsiteArgs extends ResourceArgs { + public String[] files; + public AzureStaticWebsiteArgs(String[] files) { + this.files = files; + } +} + +class AzureStaticWebsite extends ComponentResource { + public Output url; + + public AzureStaticWebsite(String name, AzureStaticWebsiteArgs args) { + this(name, args, null); + } + + public AzureStaticWebsite(String name, AzureStaticWebsiteArgs args, ComponentResourceOptions opts) { + super("quickstart:index:AzureStaticWebsite", name, args, opts); + + // Create a resource group + var resourceGroup = new ResourceGroup("my-group", null, + // Set the parent to the component (step #2) above. + // Also, do the same for all other resources below. + CustomResourceOptions.builder().parent(this).build()); + + // Create a storage account + var storageAccount = new StorageAccount("myaccount", StorageAccountArgs.builder() + .resourceGroupName(resourceGroup.name()) + .kind("StorageV2") + .sku(SkuArgs.builder() + .name("Standard_LRS") + .build()) + .build(), CustomResourceOptions.builder().parent(this).build()); + + // Enable static website support + var staticWebsite = new StorageAccountStaticWebsite("static-website", StorageAccountStaticWebsiteArgs.builder() + .accountName(storageAccount.name()) + .resourceGroupName(resourceGroup.name()) + .indexDocument("index.html") + .build(), CustomResourceOptions.builder().parent(this).build()); + + // Upload each file as a blob: + for (var file : args.files) { + new Blob(file, BlobArgs.builder() + .accountName(storageAccount.name()) + .containerName(staticWebsite.containerName()) + .resourceGroupName(resourceGroup.name()) + .source(new FileAsset(file)) + .contentType("text/html") + .build(), CustomResourceOptions.builder() + .parent(this) + .build()); + } + + // Capture the URL and make it available as a component property and output: + this.url = storageAccount.primaryEndpoints().applyValue(pe -> pe.web().get()); + this.registerOutputs(Map.of("url", this.url)); + } +} +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +{{% notes type="warning" %}} + +Unfortunately, YAML lacks the language facilities to author components. Feel free to [skip ahead](/docs/iac/get-started/azure/destroy-stack/). + +{{% /notes %}} + +{{% /choosable %}} + +### Instantiate the component + +Now go back to your original file {{< langfile >}}. Now that you have moved all of the resources, you can start over with a clean slate. +Ensure the file is empty and we will build it back up by simply importing and instantiating our new component. + +Add this to your now-empty {{< langfile >}}: + +{{% choosable language typescript %}} + +```typescript +// Import from our new component module: +import { AzureStaticWebsite } from "./website"; + +// Create an instance of our component with the same files as before: +const website = new AzureStaticWebsite("my-website", { + files: [ "index.html" ], +}); + +// And export its autoassigned URL: +export const url = website.url; +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +import pulumi + +# Import from our new component module: +from website import AzureStaticWebsite + +# Create an instance of our component with the same files as before: +website = AzureStaticWebsite('my-website', files=['index.html']) + +# And export its autoassigned URL: +pulumi.export("url", website.url) +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +package main + +import ( + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + // Create an instance of our component with the same files as before: + website, err := NewAzureStaticWebsite(ctx, "my-website", AzureStaticWebsiteArgs{ + Files: []string{"index.html"}, + }) + if err != nil { + return err + } + + // And export its autoassigned URL: + ctx.Export("url", website.Url) + return nil + }) +} + +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +using Pulumi; +using Pulumi.AzureNative.Resources; +using Pulumi.AzureNative.Storage; +using System.Collections.Generic; + +return await Pulumi.Deployment.RunAsync(() => +{ + // Create an instance of our component with the same files as before: + var website = new AzureStaticWebsite("my-website", new AzureStaticWebsiteArgs() + { + Files = new[] { "index.html" } + }); + + // And export its autoassigned URL: + return new Dictionary + { + ["url"] = website.Url + }; +}); +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +package myproject; + +import com.pulumi.Pulumi; + +public class App { + public static void main(String[] args) { + Pulumi.run(ctx -> { + // Create an instance of our component with the same files as before: + var website = new AzureStaticWebsite("my-website", + new AzureStaticWebsiteArgs(new String[] { "index.html" })); + + // And export its autoassigned URL: + ctx.export("url", website.url); + }); + } +} +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +{{% notes type="warning" %}} + +Unfortunately, YAML lacks the language facilities to author components. Feel free to [skip ahead](/docs/iac/get-started/azure/destroy-stack/). + +{{% /notes %}} + +{{% /choosable %}} + +### Deploy the component + +Now deploy the resulting component instantiation. To do so, run `pulumi up` as usual: + +``` +$ pulumi up +Previewing update (dev) + + Type Name Plan + pulumi:pulumi:Stack quickstart-dev + + ├─ quickstart:index:AzureStaticWebsite my-site create + + │ ├─ azure-native:resources:ResourceGroup my-group create + + │ ├─ azure-native:storage:StorageAccount myaccount create + + │ ├─ azure-native:storage:StorageAccountStaticWebsite static-website create + + │ └─ azure-native:storage:Blob index.html create + - ├─ azure-native:storage:Blob index.html delete + - ├─ azure-native:storage:StorageAccountStaticWebsite static-website delete + - ├─ azure-native:storage:StorageAccount myaccount delete + - └─ azure-native:resources:ResourceGroup my-group delete + +Resources: + + 5 to create + - 4 to delete + 9 changes. 1 unchanged + +Do you want to perform this update? [Use arrows to move, type to filter] + yes +> no + details +``` + +This preview shows you a few things. First, you'll see our `AzureStaticWebsite` component with all of its children +resources neatly parented underneath it. This helps to see what resources relate to which components. Next, +you'll see that your old resources are being destroyed. + +{{% notes type="info" %}} + +If you're wondering why Pulumi didn't simply update the resources in place, it's because certain changes -- like +refactoring resources into a component -- fundamentally change a resource's identity. Many changes like updating +properties or moving resources between files are not disruptive like this. In such cases, you can assign +[aliases](/docs/iac/concepts/options/aliases/) to prevent deletions from happening. + +{{% /notes %}} + +Accept the changes by selecting `yes` and the deployment will occur: + +``` +Updating (dev) + + Type Name Status + pulumi:pulumi:Stack pu-quickstart-dev + + ├─ quickstart:index:AzureStaticWebsite my-site created (0.16s) + + │ ├─ azure-native:resources:ResourceGroup my-group created (1s) + + │ ├─ azure-native:storage:StorageAccount myaccount created (2s) + + │ ├─ azure-native:storage:StorageAccountStaticWebsite static-website created (0.24s) + + │ └─ azure-native:storage:Blob index.html created (0.19s) + - ├─ azure-native:storage:Blob index.html deleted (0.18s) + - ├─ azure-native:storage:StorageAccountStaticWebsite static-website deleted (0.27s) + - ├─ azure-native:storage:StorageAccount myaccount deleted (0.51s) + - └─ azure-native:resources:ResourceGroup my-group deleted (0.58s) + +Outputs: + ~ url: "https://myaccountabc123.z13.web.core.windows.net/" => "https://myaccountxyz789.z13.web.core.windows.net/" + +Resources: + + 5 created + - 4 deleted + 9 changes. 1 unchanged + +Duration: 10s +``` + +Now test out your new website -- it works like before, just with a tidier codebase now! + +{{% choosable os "linux,macos" %}} + +```bash +$ curl $(pulumi stack output url) + + +

Hello, Pulumi!

+ + +``` + +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> curl (pulumi stack output url) + + +

Hello, Pulumi!

+ + +``` + +{{% /choosable %}} + +Once you are ready to move on, let's destroy everything we've spun up in this tutorial. + +{{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/azure/create-project.md b/content/docs/iac/get-started/azure/create-project.md index 3b1371f76fd9..db96791cbeed 100644 --- a/content/docs/iac/get-started/azure/create-project.md +++ b/content/docs/iac/get-started/azure/create-project.md @@ -1,161 +1,590 @@ --- title_tag: Create a New Project | Azure -meta_desc: This page provides an overview of how to create a new Azure + Pulumi project. title: Create project -h1: "Pulumi & Azure: Create project" -weight: 3 +h1: "Get started with Pulumi and Azure" +meta_desc: This page provides an overview of how to create a new Azure + Pulumi project. +weight: 4 menu: iac: name: Create project identifier: azure-get-started.create-project parent: azure-get-started - weight: 3 + weight: 4 aliases: - /docs/quickstart/azure/create-project/ - /docs/clouds/azure/get-started/create-project/ + - /docs/quickstart/azure/review-project/ + - /docs/clouds/azure/get-started/review-project/ --- -Now that you have set up your environment by installing Pulumi, installing your preferred language runtime, -and configuring your Azure credentials, let's create your first Pulumi program. +## Create a new project + +A [**project**](/docs/iac/concepts/projects) is a program in your chosen language that defines a collection of related cloud resources. In this step, you will create a new project. + +### Initializing your project + +Each project lives in its own directory. Create a new one: + +{{% choosable os "linux,macos" %}} + +```bash +$ mkdir quickstart +``` + +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> mkdir quickstart +``` + +{{% /choosable %}} + +Change into the new directory: + +{{% choosable os "linux,macos" %}} + +```bash +$ cd quickstart +``` + +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> cd quickstart +``` -{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} +{{% /choosable %}} + +Now initialize a new Pulumi project for Azure using the `pulumi new` command: {{% choosable language typescript %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new azure-typescript ``` +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi new azure-typescript +``` + +{{% /choosable %}} + {{% /choosable %}} {{% choosable language python %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new azure-python ``` +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi new azure-python +``` + +{{% /choosable %}} + {{% /choosable %}} {{% choosable language csharp %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new azure-csharp ``` +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi new azure-csharp +``` + +{{% /choosable %}} + {{% /choosable %}} {{% choosable language go %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new azure-go ``` +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi new azure-go +``` + +{{% /choosable %}} + {{% /choosable %}} {{% choosable language java %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new azure-java ``` +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi new azure-java +``` + +{{% /choosable %}} + {{% /choosable %}} {{% choosable language yaml %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new azure-yaml ``` {{% /choosable %}} +{{% choosable os "windows" %}} -The [`pulumi new`](/docs/cli/commands/pulumi_new) command creates a new Pulumi project with some basic scaffolding based on the cloud and language specified. +```powershell +> pulumi new azure-yaml +``` -{{< cli-note >}} +{{% /choosable %}} -After logging in, the CLI will proceed with walking you through creating a new project. +{{% /choosable %}} -First, you will be asked for a **project name** and **project description**. Hit `ENTER` to accept the default values or specify new values. +The `pulumi new` command interactively walks through initializing a new project, as well as creating a +[**stack**](/docs/iac/concepts/stacks) and [**configuring**](/docs/iac/concepts/config) it. A stack is an instance of your +project and you may have many of them -- like `dev`, `staging`, and `prod` -- each with different configuration settings. +You will be prompted for configuration values such as an Azure location. You can hit ENTER to accept the default of `WestUS2`, +or can type in another value such as `eastus`: + +``` +azure-native:location: The Azure location to use: (WestUS2) eastus ``` -This command will walk you through creating a new Pulumi project. -Enter a value or leave blank to accept the (default), and press . -Press ^C at any time to quit. +{{< cli-note >}} -project name: (quickstart) -project description: (A minimal Azure Native Pulumi program) -Created project 'quickstart' -``` +{{% choosable language "typescript" %}} + +After some dependency installations from `npm`, the project and stack will be ready. + +{{% /choosable %}} + +{{% choosable language python %}} + +After the command completes, the project and stack will be ready. + +{{% /choosable %}} + +{{% choosable language go %}} -Next, you will be asked for a **stack name**. Hit `ENTER` to accept the default value of `dev`. +After the command completes, the project and stack will be ready. + +{{% /choosable %}} + +{{% choosable language "csharp,fsharp,visualbasic" %}} + +After the command completes, the project and stack will be ready. + +{{% /choosable %}} + +{{% choosable language java %}} + +After the command completes, the project and stack will be ready. + +{{% /choosable %}} + +{{% choosable language yaml %}} + +After the command completes, the project and stack will be ready. + +{{% /choosable %}} +### Review your new project's contents + +If you list the contents of your directory, you'll see some key files: + +{{% choosable language java %}} + +- `src/main/java/myproject` is the project's Java package root + +{{% /choosable %}} + +{{% choosable language "typescript,python,go,csharp,java" %}} + +- {{< langfile >}} contains your project's main code that declares an Azure resource group and storage account + +- `Pulumi.yaml` is a [project file](/docs/iac/concepts/projects/project-file) containing metadata about your project like its name + +{{% /choosable %}} +{{% choosable language "yaml" %}} + +- `Pulumi.yaml` is a [project file](/docs/iac/concepts/projects/project-file) containing metadata about your project, like its name, as well as declaring your project's resources + +{{% /choosable %}} + +- `Pulumi.dev.yaml` contains configuration values for the stack you just initialized + +Now examine the code in {{< langfile >}}: + +{{% choosable language typescript %}} + +```typescript +import * as pulumi from "@pulumi/pulumi"; +import * as resources from "@pulumi/azure-native/resources"; +import * as storage from "@pulumi/azure-native/storage"; + +// Create an Azure Resource Group +const resourceGroup = new resources.ResourceGroup("resourceGroup"); + +// Create an Azure resource (Storage Account) +const storageAccount = new storage.StorageAccount("sa", { + resourceGroupName: resourceGroup.name, + sku: { + name: storage.SkuName.Standard_LRS, + }, + kind: storage.Kind.StorageV2, +}); + +const storageAccountKeys = storage.listStorageAccountKeysOutput({ + resourceGroupName: resourceGroup.name, + accountName: storageAccount.name +}); + +// Export the primary key of the Storage Account +export const primaryStorageKey = pulumi.secret(storageAccountKeys.keys[0].value); ``` -Please enter your desired stack name. -To create a stack in an organization, use the format / (e.g. `acmecorp/dev`). -stack name: (dev) -Created stack 'dev' + +{{% /choosable %}} +{{% choosable language python %}} + +```python +"""An Azure RM Python Pulumi program""" + +import pulumi +from pulumi_azure_native import storage +from pulumi_azure_native import resources + +# Create an Azure Resource Group +resource_group = resources.ResourceGroup("resource_group") + +# Create an Azure resource (Storage Account) +account = storage.StorageAccount( + "sa", + resource_group_name=resource_group.name, + sku={ + "name": storage.SkuName.STANDARD_LRS, + }, + kind=storage.Kind.STORAGE_V2, +) + +# Export the primary key of the Storage Account +primary_key = ( + pulumi.Output.all(resource_group.name, account.name) + .apply( + lambda args: storage.list_storage_account_keys( + resource_group_name=args[0], account_name=args[1] + ) + ) + .apply(lambda accountKeys: accountKeys.keys[0].value) +) + +pulumi.export("primary_storage_key", primary_key) ``` -For Azure projects, you will be prompted for the Azure location. You can accept the default value of `WestUS` or choose another location. +{{% /choosable %}} +{{% choosable language go %}} +```go +package main + +import ( + "github.com/pulumi/pulumi-azure-native-sdk/resources/v2" + "github.com/pulumi/pulumi-azure-native-sdk/storage/v2" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + // Create an Azure Resource Group + resourceGroup, err := resources.NewResourceGroup(ctx, "resourceGroup", nil) + if err != nil { + return err + } + + // Create an Azure resource (Storage Account) + account, err := storage.NewStorageAccount(ctx, "sa", &storage.StorageAccountArgs{ + ResourceGroupName: resourceGroup.Name, + Sku: &storage.SkuArgs{ + Name: storage.SkuName_Standard_LRS, + }, + Kind: storage.KindStorageV2, + }) + if err != nil { + return err + } + + // Export the primary key of the Storage Account + ctx.Export("primaryStorageKey", pulumi.All(resourceGroup.Name, account.Name).ApplyT( + func(args []interface{}) (string, error) { + resourceGroupName := args[0].(string) + accountName := args[1].(string) + accountKeys, err := storage.ListStorageAccountKeys(ctx, &storage.ListStorageAccountKeysArgs{ + ResourceGroupName: resourceGroupName, + AccountName: accountName, + }) + if err != nil { + return "", err + } + + return accountKeys.Keys[0].Value, nil + }, + )) + + return nil + }) +} ``` -azure-native:location: The Azure location to use: (WestUS2) -Saved config + +{{% /choosable %}} +{{% choosable language csharp %}} + +```csharp +using Pulumi; +using Pulumi.AzureNative.Resources; +using Pulumi.AzureNative.Storage; +using Pulumi.AzureNative.Storage.Inputs; +using System.Collections.Generic; + +return await Pulumi.Deployment.RunAsync(() => +{ + // Create an Azure Resource Group + var resourceGroup = new ResourceGroup("resourceGroup"); + + // Create an Azure resource (Storage Account) + var storageAccount = new StorageAccount("sa", new StorageAccountArgs + { + ResourceGroupName = resourceGroup.Name, + Sku = new SkuArgs + { + Name = SkuName.Standard_LRS + }, + Kind = Kind.StorageV2 + }); + + var storageAccountKeys = ListStorageAccountKeys.Invoke(new ListStorageAccountKeysInvokeArgs + { + ResourceGroupName = resourceGroup.Name, + AccountName = storageAccount.Name + }); + + var primaryStorageKey = storageAccountKeys.Apply(accountKeys => + { + var firstKey = accountKeys.Keys[0].Value; + return Output.CreateSecret(firstKey); + }); + + // Export the primary key of the Storage Account + return new Dictionary + { + ["primaryStorageKey"] = primaryStorageKey + }; +}); ``` -To list all available locations, use the `az account list-locations` command. +{{% /choosable %}} -```bash -$ az account list-locations --output table +{{% choosable language java %}} + +```java +package myproject; + +import com.pulumi.Pulumi; +import com.pulumi.azurenative.resources.ResourceGroup; +import com.pulumi.azurenative.storage.StorageAccount; +import com.pulumi.azurenative.storage.StorageAccountArgs; +import com.pulumi.azurenative.storage.StorageFunctions; +import com.pulumi.azurenative.storage.enums.Kind; +import com.pulumi.azurenative.storage.enums.SkuName; +import com.pulumi.azurenative.storage.inputs.ListStorageAccountKeysArgs; +import com.pulumi.azurenative.storage.inputs.SkuArgs; +import com.pulumi.azurenative.storage.outputs.EndpointsResponse; +import com.pulumi.core.Either; +import com.pulumi.core.Output; +import com.pulumi.deployment.InvokeOptions; + +public class App { + public static void main(String[] args) { + Pulumi.run(ctx -> { + var resourceGroup = new ResourceGroup("resourceGroup"); + var storageAccount = new StorageAccount("sa", StorageAccountArgs.builder() + .resourceGroupName(resourceGroup.name()) + .sku(SkuArgs.builder() + .name(SkuName.Standard_LRS) + .build()) + .kind(Kind.StorageV2) + .build()); + + var primaryStorageKey = getStorageAccountPrimaryKey( + resourceGroup.name(), + storageAccount.name()); + + ctx.export("primaryStorageKey", primaryStorageKey); + }); + } + + private static Output getStorageAccountPrimaryKey(Output resourceGroupName, + Output accountName) { + return Output.tuple(resourceGroupName, accountName).apply(tuple -> { + var actualResourceGroupName = tuple.t1; + var actualAccountName = tuple.t2; + var invokeResult = StorageFunctions.listStorageAccountKeys(ListStorageAccountKeysArgs.builder() + .resourceGroupName(actualResourceGroupName) + .accountName(actualAccountName) + .build(), InvokeOptions.Empty); + return Output.of(invokeResult) + .applyValue(r -> r.keys().get(0).value()) + .asSecret(); + }); + } +} ``` -You can then change the region for your stack by using the `pulumi config set` command as shown below: +{{% /choosable %}} -```bash -pulumi config set azure-native:location eastus +{{% choosable language yaml %}} + +```yaml +name: quickstart +runtime: yaml +description: A minimal Azure Native Pulumi YAML program + +resources: + # Create an Azure Resource Group + resourceGroup: + type: azure-native:resources:ResourceGroup + # Create an Azure resource (Storage Account) + sa: + type: azure-native:storage:StorageAccount + properties: + resourceGroupName: ${resourceGroup.name} + sku: + name: Standard_LRS + kind: StorageV2 + +variables: + storageAccountKeys: + fn::azure-native:storage:listStorageAccountKeys: + resourceGroupName: ${resourceGroup.name} + accountName: ${sa.name} + +outputs: + # Export the primary key of the Storage Account + primaryStorageKey: ${storageAccountKeys.keys[0].value} ``` -> What are [projects](/docs/concepts/projects/) and [stacks](/docs/concepts/stack/)? Pulumi projects and stacks let you organize Pulumi code. Consider a Pulumi _project_ to be analogous to a GitHub repo---a single place for code---and a _stack_ to be an instance of that code with a separate configuration. For instance, _Project Foo_ may have multiple stacks for different development environments (Dev, Test, or Prod), or perhaps for different cloud configurations (geographic region for example). See [Organizing Projects and Stacks](/docs/using-pulumi/organizing-projects-stacks/) for some best practices on organizing your Pulumi projects and stacks. +{{% /choosable %}} -{{% choosable language "typescript" %}} +The program declares an Azure Resource Group and Storage Account [resources](/docs/iac/concepts/resources) and exports the storage account's primary key as a [stack output](/docs/iac/concepts/stacks/#outputs). The primary key is marked as a secret to protect sensitive credential data. To demonstrate working with outputs, you'll add a non-secret output that exports the storage account's name for convenient reference. -After some dependency installations from `npm`, the project and stack will be ready. +{{% choosable language typescript %}} + +After the line that exports the primary key, add the following export to also export the storage account name: + +```typescript +// Export the primary key of the Storage Account +export const primaryStorageKey = pulumi.secret(storageAccountKeys.keys[0].value); +export const storageAccountName = storageAccount.name; // Add this line +``` {{% /choosable %}} {{% choosable language python %}} -After the command completes, the project and stack will be ready. +After the line that exports the primary key, add the following export to also export the storage account name: + +```python +pulumi.export("primary_storage_key", primary_key) +pulumi.export("storage_account_name", account.name) # Add this line +``` {{% /choosable %}} {{% choosable language go %}} -After the command completes, the project and stack will be ready. +After the `ctx.Export("primaryStorageKey", ...)` statement, add the following export to also export the storage account name: + +```go +// Export the primary key of the Storage Account +ctx.Export("primaryStorageKey", pulumi.All(resourceGroup.Name, account.Name).ApplyT( + func(args []interface{}) (string, error) { + // ... existing code removed for brevity ... + }, +)) +ctx.Export("storageAccountName", account.Name) // Add this line + +return nil +``` {{% /choosable %}} -{{% choosable language "csharp,fsharp,visualbasic" %}} +{{% choosable language csharp %}} -After the command completes, the project and stack will be ready. +Update the returned Dictionary to also export the storage account name: + +```csharp +return new Dictionary +{ + ["primaryStorageKey"] = primaryStorageKey, + ["storageAccountName"] = storageAccount.Name // Add this line +}; +``` {{% /choosable %}} {{% choosable language java %}} -After the command completes, the project and stack will be ready. +After `ctx.export("primaryStorageKey", ...)`, add the following export to also export the storage account name: + +```java +ctx.export("primaryStorageKey", primaryStorageKey); +ctx.export("storageAccountName", storageAccount.name()); // Add this line +``` {{% /choosable %}} {{% choosable language yaml %}} -After the command completes, the project and stack will be ready. +In the `outputs` section, add the following line to also export the storage account name: + +```yaml +outputs: + # Export the primary key of the Storage Account + primaryStorageKey: ${storageAccountKeys.keys[0].value} + storageAccountName: ${sa.name} # Add this line +``` {{% /choosable %}} -Next, we'll review the generated project files. +Now you're ready for your first deployment! {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/azure/deploy-changes.md b/content/docs/iac/get-started/azure/deploy-changes.md deleted file mode 100644 index ebcda6d5493f..000000000000 --- a/content/docs/iac/get-started/azure/deploy-changes.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -title_tag: Deploy the Changes | Azure -meta_desc: Learn how to deploy changes to an Azure project in this guide. -title: Deploy changes -h1: "Pulumi & Azure: Deploy changes" -weight: 7 -menu: - iac: - name: Deploy changes - identifier: azure-get-started.deploy-changes - parent: azure-get-started - weight: 7 -aliases: - - /docs/quickstart/azure/deploy-changes/ - - /docs/clouds/azure/get-started/deploy-changes/ ---- - -Deploy your changes by using `pulumi up` again. - -```bash -$ pulumi up -``` - -Pulumi will run the `preview` step of the update, which computes the minimally disruptive change to achieve the desired state described by the program. - -``` -Previewing update (dev): - - Type Name Plan - pulumi:pulumi:Stack quickstart-dev - + ├─ azure-native:storage:StorageAccountStaticWebsite staticWebsite create - + └─ azure-native:storage:Blob index.html create - -Outputs: - + staticEndpoint : "https://sa8dd8af62.z22.web.core.windows.net/" - -Resources: - + 2 to create - 3 unchanged - -Do you want to perform this update? -> yes - no - details -``` - -Choosing `yes` will proceed with the update by uploading the `index.html` file to a storage container in your account and enabling static website support on the container. - -``` -Do you want to perform this update? yes -Updating (dev): - - Type Name Status - pulumi:pulumi:Stack quickstart-dev - + ├─ azure-native:storage:StorageAccountStaticWebsite staticWebsite created - + └─ azure-native:storage:Blob index.html created - -Outputs: - primaryStorageKey: "" - + staticEndpoint : "https://sa8dd8af62.z22.web.core.windows.net/" - -Resources: - + 2 created - 3 unchanged - -Duration: 4s -``` - -You can check out your new static website at the URL in the `Outputs` section of your update or you can make a `curl` request and see the contents of your `index.html` object printed out in your terminal. - -{{% choosable language typescript %}} - -```bash -$ curl $(pulumi stack output staticEndpoint) -``` - -{{% /choosable %}} - -{{% choosable language python %}} - -```bash -$ curl $(pulumi stack output staticEndpoint) -``` - -{{% /choosable %}} - -{{% choosable language go %}} - -```bash -$ curl $(pulumi stack output staticEndpoint) -``` - -{{% /choosable %}} - -{{% choosable language csharp %}} - -```bash -$ curl $(pulumi stack output staticEndpoint) -``` - -{{% /choosable %}} - -{{% choosable language java %}} - -```bash -$ curl $(pulumi stack output staticEndpoint) -``` - -{{% /choosable %}} - -{{% choosable language yaml %}} - -```bash -$ curl $(pulumi stack output staticEndpoint) -``` - -{{% /choosable %}} - -And in a few moments, you should see: - -```bash - - -

Hello, Pulumi!

- - -``` - -Now that you have deployed your site, you will destroy the resources. - -{{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/azure/deploy-stack.md b/content/docs/iac/get-started/azure/deploy-stack.md index 01192b1579df..36bd9c48500f 100644 --- a/content/docs/iac/get-started/azure/deploy-stack.md +++ b/content/docs/iac/get-started/azure/deploy-stack.md @@ -1,12 +1,12 @@ --- title_tag: Deploy the Stack | Azure +title: Deploy to Azure +h1: "Get started with Pulumi and Azure" meta_desc: Learn how to deploy your stack to an Azure project in this guide. -title: Deploy stack -h1: "Pulumi & Azure: Deploy stack" weight: 5 menu: iac: - name: Deploy stack + name: Deploy identifier: azure-get-started.deploy-stack parent: azure-get-started weight: 5 @@ -15,13 +15,15 @@ aliases: - /docs/clouds/azure/get-started/deploy-stack/ --- -Let's go ahead and deploy your stack: +## Deploy to Azure + +Now run `pulumi up` to start deploying your new storage account: ```bash $ pulumi up ``` -This command evaluates your program and determines the resource updates to make. First, a preview is shown that outlines the changes that will be made when you run the update: +This command first shows you a **preview** of the changes that will be made: ``` Previewing update (dev): @@ -31,6 +33,10 @@ Previewing update (dev): + ├─ azure-native:resources:ResourceGroup resourceGroup create + └─ azure-native:storage:StorageAccount sa create +Outputs: + primaryStorageKey : [unknown] + storageAccountName: [unknown] + Resources: + 3 to create @@ -40,82 +46,87 @@ Do you want to perform this update? details ``` -Once the preview has finished, you are given three options to choose from. Choosing `details` will show you a rich diff of the changes to be made. Choosing `yes` will create your new storage account in Azure. Choosing `no` will return you to the user prompt without performing the update operation. +No changes have been made yet. You may decline to proceed by selecting `no` or choose `details` to +see more information about the proposed update like your storage account's properties. + +### Performing the update + +To proceed and deploy your new storage account, select `yes`. This begins an **update**: ``` Do you want to perform this update? yes -Updating (dev): +Updating (dev) - Type Name Status - + pulumi:pulumi:Stack quickstart-dev created - + ├─ azure-native:resources:ResourceGroup resourceGroup created - + └─ azure-native:storage:StorageAccount sa created +View in Browser (Ctrl+O): https://app.pulumi.com/your-org-name/quickstart/dev/updates/1 + + Type Name Status + + pulumi:pulumi:Stack quickstart-dev created (25s) + + ├─ azure-native:resources:ResourceGroup resourceGroup created (2s) + + └─ azure-native:storage:StorageAccount sa created (20s) Outputs: - primaryStorageKey: "" + primaryStorageKey : [secret] + storageAccountName: "sa8deefa78" Resources: + 3 created -Duration: 26s +Duration: 27s ``` -Remember the output you defined in the previous step? That [stack output](/docs/concepts/stack#outputs) can be seen in the `Outputs:` section of your update. You can access your outputs from the CLI by running the `pulumi stack output [property-name]` command. For example you can print the primary key of your bucket with the following command: - -{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} +Updates can take some time since they wait for the cloud resources to finish being created. Storage accounts +may take a bit longer, so the update could finish in 20-30 seconds. -{{% choosable language typescript %}} +{{< auto-naming-note resource="storage account" suffix="a1b2c3d" >}} -```bash -$ pulumi stack output primaryStorageKey -``` +### Using stack outputs -{{% /choosable %}} +Both the storage account name and primary key are available as stack outputs. To view the storage account name: -{{% choosable language python %}} +{{% choosable language "typescript,go,csharp,java,yaml" %}} ```bash -$ pulumi stack output primary_storage_key +$ pulumi stack output storageAccountName ``` {{% /choosable %}} -{{% choosable language go %}} +{{% choosable language python %}} ```bash -$ pulumi stack output primaryStorageKey +$ pulumi stack output storage_account_name ``` {{% /choosable %}} -{{% choosable language csharp %}} +Running that command will print out the storage account's name. -```bash -$ pulumi stack output primaryStorageKey -``` +The primary key is marked as a secret. To view it, use the `--show-secrets` flag: -{{% /choosable %}} - -{{% choosable language java %}} +{{% choosable language "typescript,go,csharp,java,yaml" %}} ```bash -$ pulumi stack output primaryStorageKey +$ pulumi stack output primaryStorageKey --show-secrets ``` {{% /choosable %}} -{{% choosable language yaml %}} +{{% choosable language python %}} ```bash -$ pulumi stack output primaryStorageKey +$ pulumi stack output primary_storage_key --show-secrets ``` {{% /choosable %}} -Running that command will print out the storage account's primary key. +### View your update on Pulumi Cloud + +If you are logged into [Pulumi Cloud](/docs/pulumi-cloud), you'll see "View Live" hyperlinks in the CLI output during your update. These go to [a page](https://app.pulumi.com) with detailed information about your stack including resources, configuration, a full history of updates, and more. Navigate to it to review the details of your update: -{{< console-note >}} + + A stack update with console output, as shown in the Pulumi Service + -Now that your storage account has been provisioned, let's modify it to host a static website. +Now that the storage account has been provisioned, you'll update it to host a static website. {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/azure/destroy-stack.md b/content/docs/iac/get-started/azure/destroy-stack.md index 96b30537d228..fbbbd54d4a6f 100644 --- a/content/docs/iac/get-started/azure/destroy-stack.md +++ b/content/docs/iac/get-started/azure/destroy-stack.md @@ -1,12 +1,12 @@ --- title_tag: Destroy the Stack | Azure -meta_desc: This page provides an overview of how to destroy a Pulumi stack of an Azure project. title: Destroy stack -h1: "Pulumi & Azure: Destroy stack" +h1: "Get started with Pulumi and Azure" +meta_desc: This page provides an overview of how to destroy a Pulumi stack of an Azure project. weight: 8 menu: iac: - name: Destroy stack + name: Cleanup & destroy identifier: azure-get-started.destroy-stack parent: azure-get-started weight: 8 @@ -15,15 +15,27 @@ aliases: - /docs/clouds/azure/get-started/destroy-stack/ --- -Now that you've seen how to deploy changes to our program, let's clean up and tear down the resources that are part of your stack. +## Cleanup & destroy the stack + +Our final step is to clean up all of the resources we've provisioned. This is as simple as running `pulumi destroy`: -To destroy resources, run the following: +{{% choosable os "linux,macos" %}} ```bash -pulumi destroy +$ pulumi destroy +``` + +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> pulumi destroy ``` -You'll be prompted to make sure you really want to delete these resources. This can take a minute or two; Pulumi waits until all resources are shut down and deleted before it considers the destroy operation to be complete. +{{% /choosable %}} + +Just like `pulumi up`, `pulumi destroy` shows you a preview before performing any changes: ``` Previewing destroy (dev): @@ -36,13 +48,20 @@ Previewing destroy (dev): - └─ azure-native:resources:ResourceGroup resourceGroup delete Outputs: - - primaryStorageKey: "" - - staticEndpoint : "https://sa8dd8af62.z22.web.core.windows.net/" + - url: "https://sa8dd8af62.z22.web.core.windows.net/" Resources: - 5 to delete -Do you want to perform this destroy? yes +Do you want to perform this destroy? +> yes + no + details +``` + +As with an update, we can choose `no` or `details`; select `yes` to proceed: + +``` Destroying (dev): Type Name Status @@ -53,8 +72,7 @@ Destroying (dev): - └─ azure-native:resources:ResourceGroup resourceGroup deleted Outputs: - - primaryStorageKey: "" - - staticEndpoint : "https://sa8dd8af62.z22.web.core.windows.net/" + - url: "https://sa8dd8af62.z22.web.core.windows.net/" Resources: - 5 deleted @@ -62,17 +80,29 @@ Resources: Duration: 53s ``` -To delete the stack itself, run [`pulumi stack rm`](/docs/cli/commands/pulumi_stack_rm). Note that this removes the stack -entirely from Pulumi Cloud, along with all of its update history. +At this stage, your stack still exists, but all cloud resources have been deleted from it. + +## Remove the stack + +The final step is to remove the stack itself. Destroy keeps the stack around so that you still have the full +history of what happened to the stack. Running [`pulumi stack rm`](/docs/cli/commands/pulumi_stack_rm) will +delete it entirely, including all history and state snapshots. Be careful, this step cannot be undone! + +{{% choosable "os" "macos,linux" %}} + +```bash +$ pulumi stack rm +``` + +{{% /choosable %}} +{{% choosable "os" "windows" %}} -Congratulations! You've successfully provisioned some cloud resources using Pulumi. By completing this guide you have successfully: +```powershell +> pulumi stack rm +``` -- Created a Pulumi new project. -- Provisioned a new Azure storage account and container. -- Added an `index.html` file to your container. -- Served the `index.html` as a static website. -- Destroyed the resources you've provisioned. +{{% /choosable %}} -On the next page, we have a collection of examples and tutorials that you can deploy as they are or use them as a foundation for your own applications and infrastructure projects. +You'll be prompted to confirm the removal. Confirm it to successfully complete this tutorial. {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/azure/modify-program.md b/content/docs/iac/get-started/azure/modify-program.md index 9d951c669d08..d691059c35af 100644 --- a/content/docs/iac/get-started/azure/modify-program.md +++ b/content/docs/iac/get-started/azure/modify-program.md @@ -1,21 +1,40 @@ --- -title_tag: Modify the Program | Azure +title_tag: Make an Update | Azure +title: Make an update +h1: "Get started with Pulumi and Azure" meta_desc: This page provides an overview on how to update an Azure project from a Pulumi program. -title: Modify program -h1: "Pulumi & Azure: Modify program" weight: 6 menu: iac: - name: Modify program + name: Make an update identifier: azure-get-started.modify-program parent: azure-get-started weight: 6 aliases: - /docs/quickstart/azure/modify-program/ - /docs/clouds/azure/get-started/modify-program/ + - /docs/quickstart/azure/deploy-changes/ + - /docs/clouds/azure/get-started/deploy-changes/ --- -Now that your storage account is provisioned, let's add an object to it. First, from within your project directory, create a new `index.html` file with some content in it. +## Make an update + +Now you will update your project to serve a static website out of your Azure storage account. You will change your code and then re-run `pulumi up` which will update your infrastructure. + +### Add new resources + +Pulumi knows how to evolve your current infrastructure to your project's new desired state, both for the first deployment as well as subsequent updates. + +To turn your storage account into a static website, you will add two new Azure resources: + +1. [`StorageAccountStaticWebsite`](/registry/packages/azure-native/api-docs/storage/storageaccountstaticwebsite/): + enables static website support on your storage account +2. [`Blob`](/registry/packages/azure-native/api-docs/storage/blob/): + uploads your website content to the storage container + +### Add an index.html + +First, from within your project directory, create a new `index.html` file with some content in it. {{< chooser os "macos,linux,windows" / >}} @@ -61,16 +80,17 @@ EOT {{% /choosable %}} -Now that you have your new `index.html` with some content, you can enable static website support, upload `index.html` to a storage container, and retrieve a public URL through the use of resource properties. These properties can be used to define dependencies between related resources or to retrieve property values for further processing. - -{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} +Now open {{< langfile >}} in your editor and enable static website support by adding a [`StorageAccountStaticWebsite`](/registry/packages/azure-native/api-docs/storage/storageaccountstaticwebsite/) resource right after the storage account is created: {{% choosable language typescript %}} -To start, open `index.ts` and add the following right after the storage account creation: - ```typescript -// Enable static website support +// Create an Azure resource (Storage Account) +const storageAccount = new storage.StorageAccount("sa", { + /* existing storage account configuration */ +}); + +// Enable static website support - add this code const staticWebsite = new storage.StorageAccountStaticWebsite("staticWebsite", { accountName: storageAccount.name, resourceGroupName: resourceGroup.name, @@ -81,10 +101,14 @@ const staticWebsite = new storage.StorageAccountStaticWebsite("staticWebsite", { {{% /choosable %}} {{% choosable language python %}} -To start, open `__main__.py` and add the following right after the storage account creation: - ```python -# Enable static website support +# Create an Azure resource (Storage Account) +account = storage.StorageAccount( + "sa", + # existing storage account configuration +) + +# Enable static website support - add this code static_website = storage.StorageAccountStaticWebsite( "staticWebsite", account_name=account.name, @@ -96,10 +120,16 @@ static_website = storage.StorageAccountStaticWebsite( {{% /choosable %}} {{% choosable language go %}} -To start, open `main.go` and add the following right after the storage account creation: - ```go -// Enable static website support +// Create an Azure resource (Storage Account) +account, err := storage.NewStorageAccount(ctx, "sa", &storage.StorageAccountArgs{ + // existing storage account configuration +}) +if err != nil { + return err +} + +// Enable static website support - add this code staticWebsite, err := storage.NewStorageAccountStaticWebsite(ctx, "staticWebsite", &storage.StorageAccountStaticWebsiteArgs{ AccountName: account.Name, ResourceGroupName: resourceGroup.Name, @@ -113,10 +143,14 @@ if err != nil { {{% /choosable %}} {{% choosable language csharp %}} -To start, open `Program.cs` and add the following right after the storage account creation: - ```csharp -// Enable static website support +// Create an Azure resource (Storage Account) +var storageAccount = new StorageAccount("sa", new StorageAccountArgs +{ + /* existing storage account configuration */ +}); + +// Enable static website support - add this code var staticWebsite = new StorageAccountStaticWebsite("staticWebsite", new StorageAccountStaticWebsiteArgs { AccountName = storageAccount.Name, @@ -129,7 +163,7 @@ var staticWebsite = new StorageAccountStaticWebsite("staticWebsite", new Storage {{% choosable language java %}} -To start, open `App.java` and add the following imports: +First, add the following imports at the top of `App.java`: ```java import com.pulumi.azurenative.storage.StorageAccountStaticWebsite; @@ -140,9 +174,15 @@ import com.pulumi.azurenative.storage.outputs.EndpointsResponse; import com.pulumi.asset.FileAsset; ``` -Next, add the following right after the storage account creation: +Then add the following right after the storage account creation: ```java +// Create an Azure resource (Storage Account) +var storageAccount = new StorageAccount("sa", StorageAccountArgs.builder() + // existing storage account configuration + .build()); + +// Enable static website support - add this code var staticWebsite = new StorageAccountStaticWebsite("staticWebsite", StorageAccountStaticWebsiteArgs.builder() .accountName(storageAccount.name()) @@ -155,11 +195,14 @@ var staticWebsite = new StorageAccountStaticWebsite("staticWebsite", {{% choosable language yaml %}} -To start, open `Pulumi.yaml` and add the following right after the storage account creation: - ```yaml resources: - # ... + # Create an Azure resource (Storage Account) + sa: + type: azure-native:storage:StorageAccount + # existing storage account configuration + + # Enable static website support - add this code staticWebsite: type: azure-native:storage:StorageAccountStaticWebsite properties: @@ -170,9 +213,10 @@ resources: {{% /choosable %}} -The static website resource leverages the storage account and resource group names defined previously in your program. +Notice that resources can reference each other, which forms automatic dependencies between them. +Pulumi uses this information to parallelize deployments safely. -Now use all of these cloud resources and a local `FileAsset` resource to upload `index.html` into your storage container by adding the following at the end of the file (after enabling the static website support): +Now use all of these cloud resources and a local `FileAsset` resource to upload `index.html` into your storage container by adding a [`Blob`](/registry/packages/azure-native/api-docs/storage/blob/) at the end of the file (after enabling the static website support): {{% choosable language typescript %}} ```typescript @@ -272,11 +316,19 @@ resources: {{% /choosable %}} -{{% choosable language typescript %}} +This uploads the `index.html` file to your storage container using a Pulumi concept called an [asset](/docs/iac/concepts/assets-archives/#assets). + +### Export the website URL + +Now to export the website's URL for easy access, add the `staticEndpoint` export to your return statement as shown in this example: -Finally, at the end of `index.ts`, export the resulting storage container's endpoint URL to stdout for easy access: +{{% choosable language typescript %}} ```typescript +// Export the primary key of the Storage Account +export const primaryStorageKey = pulumi.secret(storageAccountKeys.keys[0].value); +export const storageAccountName = storageAccount.name; + // Web endpoint to the website export const staticEndpoint = storageAccount.primaryEndpoints.web; ``` @@ -285,9 +337,11 @@ export const staticEndpoint = storageAccount.primaryEndpoints.web; {{% choosable language python %}} -Finally, at the end of `__main__.py`, export the resulting storage container's endpoint URL to stdout for easy access: - ```python +# Export the primary key of the Storage Account +pulumi.export("primary_storage_key", primary_key) +pulumi.export("storage_account_name", account.name) + # Web endpoint to the website pulumi.export("staticEndpoint", account.primary_endpoints.web) ``` @@ -296,9 +350,25 @@ pulumi.export("staticEndpoint", account.primary_endpoints.web) {{% choosable language go %}} -Finally, at the end of `main.go`, export the resulting storage container's endpoint URL to stdout for easy access: - ```go +// Export the primary key of the Storage Account +ctx.Export("primaryStorageKey", pulumi.All(resourceGroup.Name, account.Name).ApplyT( + func(args []interface{}) (string, error) { + resourceGroupName := args[0].(string) + accountName := args[1].(string) + accountKeys, err := storage.ListStorageAccountKeys(ctx, &storage.ListStorageAccountKeysArgs{ + ResourceGroupName: resourceGroupName, + AccountName: accountName, + }) + if err != nil { + return "", err + } + + return accountKeys.Keys[0].Value, nil + }, +)) +ctx.Export("storageAccountName", account.Name) + // Web endpoint to the website ctx.Export("staticEndpoint", account.PrimaryEndpoints.Web()) ``` @@ -307,13 +377,12 @@ ctx.Export("staticEndpoint", account.PrimaryEndpoints.Web()) {{% choosable language csharp %}} -Finally, at the end of `Program.cs`, export the resulting storage container's endpoint URL to stdout for easy access: - ```csharp -// Web endpoint to the website +// Export outputs return new Dictionary { ["primaryStorageKey"] = primaryStorageKey, + ["storageAccountName"] = storageAccount.Name, ["staticEndpoint"] = storageAccount.PrimaryEndpoints.Apply(primaryEndpoints => primaryEndpoints.Web) }; ``` @@ -322,9 +391,12 @@ return new Dictionary {{% choosable language java %}} -Finally, at the end of `App.java`, export the resulting storage container's endpoint URL to stdout for easy access: - ```java +// Export the primary key of the Storage Account +ctx.export("primaryStorageKey", primaryStorageKey); +ctx.export("storageAccountName", storageAccount.name()); + +// Web endpoint to the website ctx.export("staticEndpoint", storageAccount.primaryEndpoints() .applyValue(EndpointsResponse::web)); ``` @@ -333,16 +405,115 @@ ctx.export("staticEndpoint", storageAccount.primaryEndpoints() {{% choosable language yaml %}} -Finally, at the end of `Pulumi.yaml` in the `outputs`, export the resulting storage container's endpoint URL to stdout for easy access: - ```yaml outputs: - # ... + # Export the primary key of the Storage Account + primaryStorageKey: ${storageAccountKeys.keys[0].value} + storageAccountName: ${sa.name} + + # Web endpoint to the website staticEndpoint: ${sa.primaryEndpoints.web} ``` {{% /choosable %}} -Now that you have declared how you want your resources to be provisioned, it is time to deploy these remaining changes. +The storage account's endpoint is [an output property](/docs/iac/concepts/inputs-outputs/#outputs) +that Azure assigns at deployment time, not a raw string, meaning its value is not known in advance. + +### Deploy the changes + +To deploy the changes, run `pulumi up` again and it will figure out the deltas: + +{{% choosable "os" "macos,linux" %}} + +```bash +$ pulumi up +``` + +{{% /choosable %}} +{{% choosable "os" "windows" %}} + +```powershell +> pulumi up +``` + +{{% /choosable %}} + +Just like the first time you will see a preview of the changes before they happen: + +``` +Previewing update (dev): + + Type Name Plan + pulumi:pulumi:Stack quickstart-dev + + ├─ azure-native:storage:StorageAccountStaticWebsite staticWebsite create + + └─ azure-native:storage:Blob index.html create + +Outputs: + + staticEndpoint : "https://sa8dd8af62.z22.web.core.windows.net/" + +Resources: + + 2 to create + 3 unchanged + +Do you want to perform this update? +> yes + no + details +``` + +Choose `yes` to perform the deployment: + +``` +Do you want to perform this update? yes +Updating (dev): + + Type Name Status + pulumi:pulumi:Stack quickstart-dev + + ├─ azure-native:storage:StorageAccountStaticWebsite staticWebsite created + + └─ azure-native:storage:Blob index.html created + +Outputs: + primaryStorageKey: "" + + staticEndpoint : "https://sa8dd8af62.z22.web.core.windows.net/" + +Resources: + + 2 created + 3 unchanged + +Duration: 4s +``` + +In just a few seconds, your new website will be ready. Curl the endpoint to see it live: + +{{% choosable os "linux,macos" %}} + +```bash +$ curl $(pulumi stack output staticEndpoint) +``` + +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> curl (pulumi stack output staticEndpoint) +``` + +{{% /choosable %}} + +This will reveal your new website! + +``` + + +

Hello, Pulumi!

+ + +``` + +Feel free to experiment, such as changing the contents of `index.html` and redeploying. + +Next, wrap the website into an infrastructure abstraction. {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/azure/next-steps.md b/content/docs/iac/get-started/azure/next-steps.md index f7f6284b2b42..df25b94f43da 100644 --- a/content/docs/iac/get-started/azure/next-steps.md +++ b/content/docs/iac/get-started/azure/next-steps.md @@ -1,9 +1,10 @@ --- title_tag: Next Steps | Azure +title: Next steps +h1: Next Steps with Pulumi & Azure +stepper_link: "Congratulations!" meta_desc: This page provides a list of tutorials that take a deeper dive into Azure cloud resources. -title: Next steps -h1: "Pulumi & Azure: Next steps" weight: 9 menu: iac: @@ -16,7 +17,15 @@ aliases: - /docs/clouds/azure/get-started/next-steps/ --- -Congrats! You've deployed your first project on Microsoft Azure with Pulumi. Here are some next steps, depending on your learning style. +Congratulations! You've successfully provisioned some cloud resources using Pulumi. By completing this guide you have successfully: + +- Created a Pulumi new project. +- Provisioned a new Azure Storage account. +- Enabled the static website feature on the storage account. +- Created a website component for easy reuse. +- Destroyed all of the resources you've provisioned. + +Below are some recommended next steps, including examples and tutorials that you can explore or use them as a foundation for your own applications and infrastructure projects. Also be sure to [join the Community Slack](https://slack.pulumi.com/) to meet fellow IaC practitioners. ## Try Pulumi ESC (Environments, Secrets, and Configuration) diff --git a/content/docs/iac/get-started/azure/review-project.md b/content/docs/iac/get-started/azure/review-project.md deleted file mode 100644 index e950259f148a..000000000000 --- a/content/docs/iac/get-started/azure/review-project.md +++ /dev/null @@ -1,317 +0,0 @@ ---- -title_tag: Review the New Project | Azure -meta_desc: This page provides an overview on how to a review a new Azure project. -title: Review project -h1: "Pulumi & Azure: Review project" -weight: 4 -menu: - iac: - name: Review project - identifier: azure-get-started.review-project - parent: azure-get-started - weight: 4 - -aliases: - - /docs/quickstart/azure/review-project/ - - /docs/clouds/azure/get-started/review-project/ ---- - -Let's review some of the generated project files: - -{{% choosable language "typescript,python,go,csharp,java" %}} - -- `Pulumi.yaml` defines the [project](/docs/concepts/projects/). - -{{% /choosable %}} - -{{% choosable language yaml %}} - -- `Pulumi.yaml` defines both the [project](/docs/concepts/projects/) and the program that manages your stack resources. - -{{% /choosable %}} - -- `Pulumi.dev.yaml` contains [configuration](/docs/concepts/config/) values for the [stack](/docs/concepts/stack/) you initialized. - -{{% choosable language java %}} - -- `src/main/java/myproject` defines the project's Java package root. - -{{% /choosable %}} - -{{% choosable language python %}} - -- `__main__.py` is the Pulumi program that defines your stack resources. - -{{% /choosable %}} - -{{% choosable language "typescript,go,csharp,java" %}} - -- {{< langfile >}} is the Pulumi program that defines your stack resources. - -{{% /choosable %}} - -Let's examine {{< langfile >}}. - -{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} - -{{% choosable language typescript %}} - -```typescript -import * as pulumi from "@pulumi/pulumi"; -import * as resources from "@pulumi/azure-native/resources"; -import * as storage from "@pulumi/azure-native/storage"; - -// Create an Azure Resource Group -const resourceGroup = new resources.ResourceGroup("resourceGroup"); - -// Create an Azure resource (Storage Account) -const storageAccount = new storage.StorageAccount("sa", { - resourceGroupName: resourceGroup.name, - sku: { - name: storage.SkuName.Standard_LRS, - }, - kind: storage.Kind.StorageV2, -}); - -// Export the primary key of the Storage Account -const storageAccountKeys = storage.listStorageAccountKeysOutput({ - resourceGroupName: resourceGroup.name, - accountName: storageAccount.name -}); - -export const primaryStorageKey = storageAccountKeys.keys[0].value; -``` - -{{% /choosable %}} -{{% choosable language python %}} - -```python -"""An Azure RM Python Pulumi program""" - -import pulumi -from pulumi_azure_native import storage -from pulumi_azure_native import resources - -# Create an Azure Resource Group -resource_group = resources.ResourceGroup("resource_group") - -# Create an Azure resource (Storage Account) -account = storage.StorageAccount( - "sa", - resource_group_name=resource_group.name, - sku={ - "name": storage.SkuName.STANDARD_LRS, - }, - kind=storage.Kind.STORAGE_V2, -) - -# Export the primary key of the Storage Account -primary_key = ( - pulumi.Output.all(resource_group.name, account.name) - .apply( - lambda args: storage.list_storage_account_keys( - resource_group_name=args[0], account_name=args[1] - ) - ) - .apply(lambda accountKeys: accountKeys.keys[0].value) -) - -pulumi.export("primary_storage_key", primary_key) -``` - -{{% /choosable %}} -{{% choosable language go %}} - -```go -package main - -import ( - "github.com/pulumi/pulumi-azure-native-sdk/resources/v2" - "github.com/pulumi/pulumi-azure-native-sdk/storage/v2" - "github.com/pulumi/pulumi/sdk/v3/go/pulumi" -) - -func main() { - pulumi.Run(func(ctx *pulumi.Context) error { - // Create an Azure Resource Group - resourceGroup, err := resources.NewResourceGroup(ctx, "resourceGroup", nil) - if err != nil { - return err - } - - // Create an Azure resource (Storage Account) - account, err := storage.NewStorageAccount(ctx, "sa", &storage.StorageAccountArgs{ - ResourceGroupName: resourceGroup.Name, - AccessTier: storage.AccessTierHot, - Sku: &storage.SkuArgs{ - Name: storage.SkuName_Standard_LRS, - }, - Kind: storage.KindStorageV2, - }) - if err != nil { - return err - } - - // Export the primary key of the Storage Account - ctx.Export("primaryStorageKey", pulumi.All(resourceGroup.Name, account.Name).ApplyT( - func(args []interface{}) (string, error) { - resourceGroupName := args[0].(string) - accountName := args[1].(string) - accountKeys, err := storage.ListStorageAccountKeys(ctx, &storage.ListStorageAccountKeysArgs{ - ResourceGroupName: resourceGroupName, - AccountName: accountName, - }) - if err != nil { - return "", err - } - - return accountKeys.Keys[0].Value, nil - }, - )) - - return nil - }) -} -``` - -{{% /choosable %}} -{{% choosable language csharp %}} - -```csharp -using Pulumi; -using Pulumi.AzureNative.Resources; -using Pulumi.AzureNative.Storage; -using Pulumi.AzureNative.Storage.Inputs; -using System.Collections.Generic; - -return await Pulumi.Deployment.RunAsync(() => -{ - // Create an Azure Resource Group - var resourceGroup = new ResourceGroup("resourceGroup"); - - // Create an Azure resource (Storage Account) - var storageAccount = new StorageAccount("sa", new StorageAccountArgs - { - ResourceGroupName = resourceGroup.Name, - Sku = new SkuArgs - { - Name = SkuName.Standard_LRS - }, - Kind = Kind.StorageV2 - }); - - var storageAccountKeys = ListStorageAccountKeys.Invoke(new ListStorageAccountKeysInvokeArgs - { - ResourceGroupName = resourceGroup.Name, - AccountName = storageAccount.Name - }); - - var primaryStorageKey = storageAccountKeys.Apply(accountKeys => - { - var firstKey = accountKeys.Keys[0].Value; - return Output.CreateSecret(firstKey); - }); - - // Export the primary key of the Storage Account - return new Dictionary - { - ["primaryStorageKey"] = primaryStorageKey - }; -}); -``` - -{{% /choosable %}} - -{{% choosable language java %}} - -```java -package myproject; - -import com.pulumi.Pulumi; -import com.pulumi.azurenative.resources.ResourceGroup; -import com.pulumi.azurenative.storage.StorageAccount; -import com.pulumi.azurenative.storage.StorageAccountArgs; -import com.pulumi.azurenative.storage.StorageFunctions; -import com.pulumi.azurenative.storage.enums.Kind; -import com.pulumi.azurenative.storage.enums.SkuName; -import com.pulumi.azurenative.storage.inputs.ListStorageAccountKeysArgs; -import com.pulumi.azurenative.storage.inputs.SkuArgs; -import com.pulumi.core.Either; -import com.pulumi.core.Output; -import com.pulumi.deployment.InvokeOptions; - -public class App { - public static void main(String[] args) { - Pulumi.run(ctx -> { - var resourceGroup = new ResourceGroup("resourceGroup"); - var storageAccount = new StorageAccount("sa", StorageAccountArgs.builder() - .resourceGroupName(resourceGroup.name()) - .sku(SkuArgs.builder() - .name(SkuName.Standard_LRS) - .build()) - .kind(Kind.StorageV2) - .build()); - - var primaryStorageKey = getStorageAccountPrimaryKey( - resourceGroup.name(), - storageAccount.name()); - - ctx.export("primaryStorageKey", primaryStorageKey); - }); - } - - private static Output getStorageAccountPrimaryKey(Output resourceGroupName, - Output accountName) { - return Output.tuple(resourceGroupName, accountName).apply(tuple -> { - var actualResourceGroupName = tuple.t1; - var actualAccountName = tuple.t2; - var invokeResult = StorageFunctions.listStorageAccountKeys(ListStorageAccountKeysArgs.builder() - .resourceGroupName(actualResourceGroupName) - .accountName(actualAccountName) - .build(), InvokeOptions.Empty); - return Output.of(invokeResult) - .applyValue(r -> r.keys().get(0).value()) - .asSecret(); - }); - } -} -``` - -{{% /choosable %}} - -{{% choosable language yaml %}} - -```yaml -name: quickstart -runtime: yaml -description: A minimal Azure Native Pulumi YAML program -resources: - resourceGroup: - type: azure-native:resources:ResourceGroup - sa: - type: azure-native:storage:StorageAccount - properties: - resourceGroupName: ${resourceGroup.name} - sku: - name: Standard_LRS - kind: StorageV2 -variables: - storageAccountKeys: - fn::azure-native:storage:listStorageAccountKeys: - resourceGroupName: ${resourceGroup.name} - accountName: ${sa.name} -outputs: - primaryStorageKey: ${storageAccountKeys.keys[0].value} -``` - -{{% /choosable %}} - -This Pulumi program creates an Azure resource group and storage account and then exports the storage account's primary key. - -{{% notes %}} -In this program, the location of the resource group is set in the configuration setting `azure-native:location` (check the `Pulumi.dev.yaml` file). This is an easy way to set a global location for your program so you don't have to specify the location for each resource manually. The location for the storage account is automatically derived from the location of the resource group. To override the location for a resource, set the location property to one of Azure's [supported locations](https://azure.microsoft.com/en-us/global-infrastructure/locations/). -{{% /notes %}} - -Next, you'll deploy your stack, which will provision a resource group and your storage account. - -{{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/gcp/_index.md b/content/docs/iac/get-started/gcp/_index.md index 8c8b05a52f5a..6c257a3e30c7 100644 --- a/content/docs/iac/get-started/gcp/_index.md +++ b/content/docs/iac/get-started/gcp/_index.md @@ -1,25 +1,67 @@ --- -title_tag: Get Started with Google Cloud -meta_desc: This page provides an overview and guide on how to get started with Google Cloud. title: Google Cloud -h1: Get started with Pulumi & Google Cloud +title_tag: Get started with Pulumi and Google Cloud +h1: Get started with Pulumi and Google Cloud +meta_desc: This page provides an overview and guide on how to get started with Google Cloud. menu: iac: name: Google Cloud identifier: gcp-get-started parent: iac-get-started weight: 3 - clouds: - parent: google-cloud - identifier: google-cloud-get-started - weight: 3 + aliases: - - /docs/get-started/gcp/ - /docs/quickstart/gcp/ - - /docs/clouds/get-started/gcp - /docs/clouds/gcp/get-started/ + - /docs/iac/get-started/gcp/deploy-changes/ + - /docs/iac/get-started/gcp/review-project/ --- -{{< cloud-intro "Google Cloud" >}} +**Infrastructure as code (IaC)** lets you deploy, change, and manage infrastructure safely, consistently, +and repeatably using code rather than a graphical user interface. + +Complete this step-by-step tutorial to deploy a Google Cloud Storage-based website using IaC. + +## Before you begin + +Make sure you have the gcloud CLI installed and authenticated. Then choose your language and ensure you've performed any prerequisites: + +{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} + +{{% choosable language "typescript" %}} + +* Node.js and npm installed locally + +{{% /choosable %}} + +{{% choosable language "python" %}} + +* Python and pip, Poetry or uv installed locally + +{{% /choosable %}} + +{{% choosable language "go" %}} + +* Go installed locally + +{{% /choosable %}} + +{{% choosable language "csharp" %}} + +* .NET installed locally + +{{% /choosable %}} + +{{% choosable language "java" %}} + +* Java 11+ and Maven 3.6.1+ installed locally + +{{% /choosable %}} + +{{% choosable language "yaml" %}} + +* A text editor + +{{% /choosable %}} {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/gcp/begin.md b/content/docs/iac/get-started/gcp/begin.md index 07046fab58df..869dadbd937a 100644 --- a/content/docs/iac/get-started/gcp/begin.md +++ b/content/docs/iac/get-started/gcp/begin.md @@ -1,8 +1,9 @@ --- -title_tag: Before You Begin | Google Cloud -meta_desc: This page provides an overview on how to get started with Pulumi and Google Cloud. -title: Before you begin -h1: "Pulumi & Google Cloud: Before you begin" +title_tag: Install Pulumi | Google Cloud +title: Install Pulumi +h1: "Get started with Pulumi and Google Cloud" +stepper_link: "I'm ready to begin" +meta_desc: This page provides an overview on how to get started with Pulumi when starting a Google Cloud project. weight: 2 menu: iac: @@ -10,80 +11,17 @@ menu: identifier: gcp-get-started.begin parent: gcp-get-started weight: 2 + aliases: - - /docs/quickstart/gcp/begin/ - - /docs/quickstart/gcp/install-pulumi/ - - /docs/quickstart/gcp/install-language-runtime/ - - /docs/quickstart/gcp/configure/ - - /docs/clouds/gcp/get-started/begin/ +- /docs/quickstart/gcp/begin/ +- /docs/quickstart/gcp/install-pulumi/ +- /docs/quickstart/gcp/install-language-runtime/ +- /docs/get-started/gcp/install-pulumi/ +- /docs/get-started/gcp/install-language-runtime/ +- /docs/get-started/gcp/begin/ +- /docs/clouds/gcp/get-started/begin/ --- -Before you get started using Pulumi, let's run through a few quick steps to ensure your environment is set up correctly. - -### Install Pulumi - -{{< install-pulumi >}} -{{% notes "info" %}} -All Windows examples in this tutorial assume you are running in PowerShell. -{{% /notes %}} -{{< /install-pulumi >}} - -Next, install the required language runtime, if you have not already. - -### Install Language Runtime - -#### Choose Your Language - -{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} - -{{% choosable language "typescript" %}} -{{< install-node >}} -{{% /choosable %}} - -{{% choosable language python %}} -{{< install-python >}} -{{% /choosable %}} - -{{% choosable language go %}} -{{< install-go >}} -{{% /choosable %}} - -{{% choosable language "csharp,fsharp,visualbasic" %}} -{{< install-dotnet >}} -{{% /choosable %}} - -{{% choosable language java %}} -{{< install-java >}} -{{% /choosable %}} - -{{% choosable language yaml %}} -{{< install-yaml >}} -{{% /choosable %}} - -Finally, configure Pulumi with Google Cloud. - -### Configure Pulumi to access your Google Cloud account - -Pulumi requires cloud credentials to manage and provision resources. You must use an IAM user or service account that has **Programmatic access** with rights to deploy and manage your Google Cloud resources. - -In this guide, you will need an IAM user account with permissions that can create and populate a Cloud Storage bucket, such as those in the predefined Storage Admin (`roles/storage.admin`) or the Storage Legacy Bucket Owner (`roles/storage.legacyBucketOwner`) roles. - -When developing locally, we recommend that you install the [Google Cloud SDK](https://cloud.google.com/sdk/install) and then [authorize access with a user account](https://cloud.google.com/sdk/docs/authorizing#authorizing_with_a_user_account). - -If `gcloud` is not configured to interact with your Google Cloud project, set it with the `config` command using the project's ID: - -```bash -gcloud config set project -``` - -Next, Pulumi requires default application credentials to interact with your Google Cloud resources, so run `auth application-default login` command to obtain those credentials. - -```bash -gcloud auth application-default login -``` - -For additional information on setting and using Google Cloud credentials, see [Google Cloud Setup](/registry/packages/gcp/installation-configuration/). - -Next, you'll create a new Pulumi project. +{{< get-started-install-body >}} {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/gcp/configure.md b/content/docs/iac/get-started/gcp/configure.md new file mode 100644 index 000000000000..e3b5c7f13fae --- /dev/null +++ b/content/docs/iac/get-started/gcp/configure.md @@ -0,0 +1,139 @@ +--- +title_tag: Configure access | Google Cloud +title: Configure access +h1: "Get started with Pulumi and Google Cloud" +meta_desc: This page provides an overview on how to get started with Pulumi when starting a Google Cloud project. +weight: 3 +menu: + iac: + name: Configure access + parent: gcp-get-started + weight: 3 + identifier: gcp-get-started.configure +aliases: + - /docs/quickstart/gcp/configure/ + - /docs/get-started/gcp/configure/ + - /docs/clouds/gcp/get-started/configure/ +--- + +## Configure access to Google Cloud + +Pulumi's CLI needs access to your Google Cloud account to manage cloud resources. + +If you've already installed and initialized the gcloud CLI, Pulumi will respect and use your configuration settings. + +You must use a Google Cloud account that has rights to deploy and manage resources, such as Cloud Storage buckets. + +### Testing access + +To test that your Google Cloud access is configured properly, run: + +{{% choosable os "linux,macos" %}} + +```bash +$ gcloud config list +``` + +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> gcloud config list +``` + +{{% /choosable %}} + +If your active account and project are printed, your configuration is correct. If not, read on: + +``` +[core] +account = user@example.com +disable_usage_reporting = True +project = my-gcp-project + +Your active configuration is: [default] +``` + +You can also verify your authentication status: + +{{% choosable os "linux,macos" %}} + +```bash +$ gcloud auth list +``` + +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> gcloud auth list +``` + +{{% /choosable %}} + +### Alternative approaches + +If you don't have the gcloud CLI installed, or you plan on using Pulumi in a CI/CD pipeline, you can create a service account and download a JSON key file. Then set the `GOOGLE_CREDENTIALS` environment variable on your workstation: + +{{% choosable os "linux,macos" %}} + +```bash +$ export GOOGLE_CREDENTIALS="$(cat ~/path/to/service-account-key.json)" +``` + +{{% /choosable %}} + +{{% choosable os windows %}} + +```powershell +> $env:GOOGLE_CREDENTIALS = (Get-Content -Path "C:\path\to\service-account-key.json" -Raw) +``` + +{{% /choosable %}} + +Alternatively, you can set the path to the key file: + +{{% choosable os "linux,macos" %}} + +```bash +$ export GOOGLE_APPLICATION_CREDENTIALS="$HOME/path/to/service-account-key.json" +``` + +{{% /choosable %}} + +{{% choosable os windows %}} + +```powershell +> $env:GOOGLE_APPLICATION_CREDENTIALS = "C:\path\to\service-account-key.json" +``` + +{{% /choosable %}} + +{{% notes type="info" %}} +Consider using [Pulumi ESC's Google Cloud login support](/docs/esc/integrations/dynamic-login-credentials/gcp-login) for dynamic, +short-lived Google Cloud credentials via OpenID Connect (OIDC) instead of long-lived static credentials. This is a security best practice. +{{% /notes %}} + +You may need to set your Google Cloud project explicitly: + +{{% choosable os "linux,macos" %}} + +```bash +$ export GOOGLE_PROJECT="" +``` + +{{% /choosable %}} + +{{% choosable os windows %}} + +```powershell +> $env:GOOGLE_PROJECT = "" +``` + +{{% /choosable %}} + +For detailed information on Pulumi's use of Google Cloud credentials, see [Google Cloud Setup](/registry/packages/gcp/installation-configuration/). + +{{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/gcp/create-component.md b/content/docs/iac/get-started/gcp/create-component.md new file mode 100644 index 000000000000..e47311f187b0 --- /dev/null +++ b/content/docs/iac/get-started/gcp/create-component.md @@ -0,0 +1,821 @@ +--- +title_tag: Create a component | Google Cloud +title: Create a component +h1: "Get started with Pulumi and Google Cloud" +meta_desc: This page provides an overview on how to create infrastructure abstractions with Pulumi. +weight: 7 +menu: + iac: + name: Create a component + identifier: gcp-get-started.create-component + parent: gcp-get-started + weight: 7 + +aliases: + - /docs/quickstart/gcp/create-component/ + - /docs/clouds/gcp/get-started/create-component/ +--- + +## Create a component + +[**Components**](/docs/iac/concepts/resources/components/) are infrastructure abstractions that encapsulate +complexity and enable sharing and reuse. Instead of copy-pasting common patterns, you can encode them as components. + +You will now create your first component that packages up your Google Cloud Storage website so you can easily stamp out +entire websites in just a few lines of code: + +{{% choosable language typescript %}} + +```typescript +const website = new GcpStorageWebsite("my-website", { + files: [ "index.html" ], +}); +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +website = GcpStorageWebsite('my-website', files=['index.html']) +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +website, err := NewGcpStorageWebsite(ctx, "my-website", GcpStorageWebsiteArgs{ + Files: []string{"index.html"}, +}) +if err != nil { + return err +} +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +var website = new GcpStorageWebsite("my-website", new GcpStorageWebsiteArgs() +{ + Files = new[] { "index.html" } +}); +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +var website = new GcpStorageWebsite("my-website", + new GcpStorageWebsiteArgs(new String[] { "index.html" })); +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +{{% notes type="warning" %}} + +Unfortunately, YAML lacks the language facilities to author components. Feel free to [skip ahead](/docs/iac/get-started/gcp/destroy-stack/). + +{{% /notes %}} + +{{% /choosable %}} + +Using components here also has the benefit that, as the requirements for your static website change, you can +update the one component definition and have all uses of it benefit. + +### Define a new component + +To define a new component, create a class called `GcpStorageWebsite` that derives from `ComponentResource`. It'll have a mostly-empty +constructor to start with but you will add the Google Cloud Storage resources to it in the next step. You'll also define the inputs for the +component -- the `files` to add to the website -- and outputs -- a single property with the website `url`. + +To get going, create a new file {{< compfile >}} alongside {{< langfile >}} and add the following: + +{{% choosable language typescript %}} + +```typescript +import * as gcp from "@pulumi/gcp"; +import * as pulumi from "@pulumi/pulumi"; + +// Arguments for the Google Cloud Storage static website component. +export interface GcpStorageWebsiteArgs { + files: string[]; // a list of files to serve. +} + +// A component that encapsulates creating a Google Cloud Storage hosted static website. +export class GcpStorageWebsite extends pulumi.ComponentResource { + public readonly url: pulumi.Output; // the website url. + + constructor(name: string, args: GcpStorageWebsiteArgs, opts?: pulumi.ComponentResourceOptions) { + super("quickstart:index:GcpStorageWebsite", name, args, opts); + + // Component initialization will go here next... + + this.registerOutputs({}); // Signal component completion. + } +} +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +import pulumi +from pulumi_gcp import storage +from typing import List + +# A component that encapsulates creating a Google Cloud Storage hosted static website. +class GcpStorageWebsite(pulumi.ComponentResource): + def __init__(self, name: str, files: List[str] = None, opts = None): + super().__init__('quickstart:index:GcpStorageWebsite', name, { 'files': files }, opts) + + # Component initialization will go here next... + + self.register_outputs({}) # Signal component completion. +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +package main + +import ( + "github.com/pulumi/pulumi-gcp/sdk/v7/go/gcp/storage" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type GcpStorageWebsite struct { + pulumi.ResourceState + Url pulumi.StringOutput // the website url. +} + +type GcpStorageWebsiteArgs struct { + Files []string // a list of files to serve. +} + +func NewGcpStorageWebsite(ctx *pulumi.Context, name string, args GcpStorageWebsiteArgs, opts ...pulumi.ResourceOption) (*GcpStorageWebsite, error) { + self := &GcpStorageWebsite{} + err := ctx.RegisterComponentResource("quickstart:index:GcpStorageWebsite", name, self, opts...) + if err != nil { + return nil, err + } + + // Component initialization will go here next... + + ctx.RegisterResourceOutputs(self, pulumi.Map{}) // Signal component completion. + return self, nil +} +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +using Pulumi; +using Pulumi.Gcp.Storage; +using Pulumi.Gcp.Storage.Inputs; +using System.Collections.Generic; + +public class GcpStorageWebsiteArgs +{ + public string[]? Files { get; set; } +} + +public class GcpStorageWebsite : Pulumi.ComponentResource +{ + public Output Url { get; private set; } + + public GcpStorageWebsite(string name, GcpStorageWebsiteArgs args, ComponentResourceOptions? opts = null) + : base("quickstart:index:GcpStorageWebsite", name, opts) + { + // Component initialization will go here next... + + this.RegisterOutputs(new Dictionary{}); // Signal component completion. + } +} +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +package myproject; + +import com.pulumi.resources.ComponentResource; +import com.pulumi.resources.ComponentResourceOptions; +import com.pulumi.core.Output; + +public class GcpStorageWebsiteArgs { + public String[] files; + public GcpStorageWebsiteArgs(String[] files) { + this.files = files; + } +} + +public class GcpStorageWebsite extends ComponentResource { + public Output url; + + public GcpStorageWebsite(String name, GcpStorageWebsiteArgs args, ComponentResourceOptions opts) { + super("quickstart:index:GcpStorageWebsite", name, args, opts); + + // Component initialization will go here next... + + this.registerOutputs(java.util.Map.of()); + } +} +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +{{% notes type="warning" %}} + +Unfortunately, YAML lacks the language facilities to author components. Feel free to [skip ahead](/docs/iac/get-started/gcp/destroy-stack/). + +{{% /notes %}} + +{{% /choosable %}} + +This defines a component but it doesn't do much yet. + +### Refactor your code into the component + +Next, make four changes: + +1. Move all resources from {{< langfile >}} into the component's constructor +2. Change each resource to use the component [as the `parent`](/docs/iac/concepts/options/parent/) +3. Generalize the creation of bucket objects by looping over the list of `files` +4. Assign the resulting website URL to the `url` property of the component + +The resulting {{< compfile >}} file will look like this; you can make each edit one at a time if preferred +to get a feel for things, or simply paste the contents of this into {{< compfile >}}: + +{{% choosable language typescript %}} + +```typescript +import * as gcp from "@pulumi/gcp"; +import * as pulumi from "@pulumi/pulumi"; + +// Arguments for the Google Cloud Storage static website component. +export interface GcpStorageWebsiteArgs { + files: string[]; // a list of files to serve. +} + +// A component that encapsulates creating a Google Cloud Storage hosted static website. +export class GcpStorageWebsite extends pulumi.ComponentResource { + public readonly url: pulumi.Output; // the website url. + + constructor(name: string, args: GcpStorageWebsiteArgs, opts?: pulumi.ComponentResourceOptions) { + super("quickstart:index:GcpStorageWebsite", name, args, opts); + + // Create a Google Cloud resource (Storage Bucket) configured for website hosting + const bucket = new gcp.storage.Bucket("my-bucket", { + location: "US", + website: { + mainPageSuffix: "index.html", + }, + uniformBucketLevelAccess: true, + }, { + parent: this, + }); + + // Allow public access to the objects + const bucketBinding = new gcp.storage.BucketIAMBinding("my-bucket-binding", { + bucket: bucket.name, + role: "roles/storage.objectViewer", + members: ["allUsers"], + }, { parent: this }); + + // Create a Bucket object for each file + for (const file of args.files) { + new gcp.storage.BucketObject(file, { + bucket: bucket.name, + name: file, + source: new pulumi.asset.FileAsset(file), + }, { parent: this, dependsOn: [bucketBinding] }); + } + + // Capture the URL and make it available as a component property and output: + this.url = pulumi.concat("http://storage.googleapis.com/", bucket.name, "/", args.files[0]); + this.registerOutputs({ url: this.url }); // Signal component completion. + } +} +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +import pulumi +from pulumi_gcp import storage +from typing import List + +# A component that encapsulates creating a Google Cloud Storage hosted static website. +class GcpStorageWebsite(pulumi.ComponentResource): + def __init__(self, name: str, files: List[str] = None, opts = None): + super().__init__('quickstart:index:GcpStorageWebsite', name, { 'files': files }, opts) + + # Create a Google Cloud resource (Storage Bucket) configured for website hosting + bucket = storage.Bucket( + 'my-bucket', + location="US", + website=\{ + "main_page_suffix": "index.html" + \}, + uniform_bucket_level_access=True, + opts=pulumi.ResourceOptions(parent=self), + ) + + # Allow public access to the objects + bucket_binding = storage.BucketIAMBinding( + "my-bucket-binding", + bucket=bucket.name, + role="roles/storage.objectViewer", + members=["allUsers"], + opts=pulumi.ResourceOptions(parent=self), + ) + + # Create a Bucket object for each file + for file in files or []: + storage.BucketObject( + file, + bucket=bucket.name, + name=file, + source=pulumi.FileAsset(file), + opts=pulumi.ResourceOptions(parent=self, depends_on=[bucket_binding]), + ) + + # Capture the URL and make it available as a component property and output + self.url = pulumi.Output.concat( + "http://storage.googleapis.com/", bucket.name, "/", files[0] + ) + self.register_outputs({ 'url': self.url }) # Signal component completion. +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +package main + +import ( + "github.com/pulumi/pulumi-gcp/sdk/v7/go/gcp/storage" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type GcpStorageWebsite struct { + pulumi.ResourceState + Url pulumi.StringOutput // the website url. +} + +type GcpStorageWebsiteArgs struct { + Files []string // a list of files to serve. +} + +func NewGcpStorageWebsite(ctx *pulumi.Context, name string, args GcpStorageWebsiteArgs, opts ...pulumi.ResourceOption) (*GcpStorageWebsite, error) { + self := &GcpStorageWebsite{} + err := ctx.RegisterComponentResource("quickstart:index:GcpStorageWebsite", name, self, opts...) + if err != nil { + return nil, err + } + + // Create a Google Cloud resource (Storage Bucket) configured for website hosting + bucket, err := storage.NewBucket(ctx, "my-bucket", &storage.BucketArgs{ + Location: pulumi.String("US"), + Website: storage.BucketWebsiteArgs{ + MainPageSuffix: pulumi.String("index.html"), + }, + UniformBucketLevelAccess: pulumi.Bool(true), + }, pulumi.Parent(self)) + if err != nil { + return nil, err + } + + // Allow public access to the objects + bucketBinding, err := storage.NewBucketIAMBinding(ctx, "my-bucket-binding", &storage.BucketIAMBindingArgs{ + Bucket: bucket.Name, + Role: pulumi.String("roles/storage.objectViewer"), + Members: pulumi.StringArray{ + pulumi.String("allUsers"), + }, + }, pulumi.Parent(self)) + if err != nil { + return nil, err + } + + // Create a Bucket object for each file + for _, file := range args.Files { + _, err := storage.NewBucketObject(ctx, file, &storage.BucketObjectArgs{ + Bucket: bucket.Name, + Name: pulumi.String(file), + Source: pulumi.NewFileAsset(file), + }, pulumi.Parent(self), pulumi.DependsOn([]pulumi.Resource{bucketBinding})) + if err != nil { + return nil, err + } + } + + // Export the website URL + self.Url = pulumi.Sprintf("http://storage.googleapis.com/%s/%s", bucket.Name, args.Files[0]) + ctx.RegisterResourceOutputs(self, pulumi.Map{"url": self.Url}) // Signal component completion. + return self, nil +} +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +using Pulumi; +using Pulumi.Gcp.Storage; +using Pulumi.Gcp.Storage.Inputs; +using System.Collections.Generic; + +public class GcpStorageWebsiteArgs +{ + public string[]? Files { get; set; } +} + +public class GcpStorageWebsite : Pulumi.ComponentResource +{ + public Output Url { get; private set; } + + public GcpStorageWebsite(string name, GcpStorageWebsiteArgs args, ComponentResourceOptions? opts = null) + : base("quickstart:index:GcpStorageWebsite", name, opts) + { + // Create a Google Cloud resource (Storage Bucket) configured for website hosting + var bucket = new Bucket("my-bucket", new BucketArgs + { + Location = "US", + Website = new BucketWebsiteArgs + { + MainPageSuffix = "index.html" + }, + UniformBucketLevelAccess = true, + }, new CustomResourceOptions + { + Parent = this, + }); + + // Allow public access to the objects + var bucketBinding = new BucketIAMBinding("my-bucket-binding", new BucketIAMBindingArgs + { + Bucket = bucket.Name, + Role = "roles/storage.objectViewer", + Members = new[] + { + "allUsers", + }, + }, new CustomResourceOptions + { + Parent = this, + }); + + // Create a Bucket object for each file + foreach (var file in args.Files ?? []) { + new BucketObject(file, new BucketObjectArgs + { + Bucket = bucket.Name, + Name = file, + Source = new FileAsset(file), + }, new CustomResourceOptions + { + Parent = this, + DependsOn = { bucketBinding } + }); + } + + // Capture the URL and make it available as a component property and output + this.Url = Output.Format($"http://storage.googleapis.com/{bucket.Name}/{args.Files?[0]}"); + this.RegisterOutputs(new Dictionary{ + ["url"] = this.Url + }); + } +} +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +package myproject; + +import com.pulumi.*; +import com.pulumi.core.*; +import com.pulumi.asset.FileAsset; +import com.pulumi.resources.*; + +import com.pulumi.gcp.storage.*; +import com.pulumi.gcp.storage.inputs.*; + +import java.util.Map; + +class GcpStorageWebsiteArgs extends ResourceArgs { + public String[] files; + public GcpStorageWebsiteArgs(String[] files) { + this.files = files; + } +} + +class GcpStorageWebsite extends ComponentResource { + public Output url; + + public GcpStorageWebsite(String name, GcpStorageWebsiteArgs args) { + this(name, args, null); + } + + public GcpStorageWebsite(String name, GcpStorageWebsiteArgs args, ComponentResourceOptions opts) { + super("quickstart:index:GcpStorageWebsite", name, args, opts); + + // Create a Google Cloud resource (Storage Bucket) configured for website hosting + var bucket = new Bucket("my-bucket", BucketArgs.builder() + .location("US") + .website(BucketWebsiteArgs.builder() + .mainPageSuffix("index.html") + .build()) + .uniformBucketLevelAccess(true) + .build(), CustomResourceOptions.builder().parent(this).build()); + + // Allow public access to the objects + var bucketBinding = new BucketIAMBinding("my-bucket-binding", BucketIAMBindingArgs.builder() + .bucket(bucket.name()) + .role("roles/storage.objectViewer") + .members("allUsers") + .build(), CustomResourceOptions.builder().parent(this).build()); + + // Create a Bucket object for each file + for (var file : args.files) { + new BucketObject(file, BucketObjectArgs.builder() + .bucket(bucket.name()) + .name(file) + .source(new FileAsset(file)) + .build(), CustomResourceOptions.builder() + .parent(this) + .dependsOn(bucketBinding) + .build()); + } + + // Capture the URL and make it available as a component property and output + this.url = Output.format("http://storage.googleapis.com/%s/%s", bucket.name(), args.files[0]); + this.registerOutputs(Map.of("url", this.url)); + } +} +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +{{% notes type="warning" %}} + +Unfortunately, YAML lacks the language facilities to author components. Feel free to [skip ahead](/docs/iac/get-started/gcp/destroy-stack/). + +{{% /notes %}} + +{{% /choosable %}} + +### Instantiate the component + +Now go back to your original file {{< langfile >}}. Now that you have moved all of the resources, you can start over with a clean slate. +Ensure the file is empty and we will build it back up by simply importing and instantiating our new component. + +Add this to your now-empty {{< langfile >}}: + +{{% choosable language typescript %}} + +```typescript +// Import from our new component module: +import { GcpStorageWebsite } from "./website"; + +// Create an instance of our component with the same files as before: +const website = new GcpStorageWebsite("my-website", { + files: [ "index.html" ], +}); + +// And export its autoassigned URL: +export const url = website.url; +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +import pulumi + +# Import from our new component module: +from website import GcpStorageWebsite + +# Create an instance of our component with the same files as before: +website = GcpStorageWebsite('my-website', files=['index.html']) + +# And export its autoassigned URL: +pulumi.export("url", website.url) +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +package main + +import ( + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + // Create an instance of our component with the same files as before: + website, err := NewGcpStorageWebsite(ctx, "my-website", GcpStorageWebsiteArgs{ + Files: []string{"index.html"}, + }) + if err != nil { + return err + } + + // And export its autoassigned URL: + ctx.Export("url", website.Url) + return nil + }) +} + +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +using Pulumi; +using Pulumi.Gcp.Storage; +using System.Collections.Generic; + +return await Pulumi.Deployment.RunAsync(() => +{ + // Create an instance of our component with the same files as before: + var website = new GcpStorageWebsite("my-website", new GcpStorageWebsiteArgs() + { + Files = new[] { "index.html" } + }); + + // And export its autoassigned URL: + return new Dictionary + { + ["url"] = website.Url + }; +}); +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +package myproject; + +import com.pulumi.Pulumi; + +public class App { + public static void main(String[] args) { + Pulumi.run(ctx -> { + // Create an instance of our component with the same files as before: + var website = new GcpStorageWebsite("my-website", + new GcpStorageWebsiteArgs(new String[] { "index.html" })); + + // And export its autoassigned URL: + ctx.export("url", website.url); + }); + } +} +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +{{% notes type="warning" %}} + +Unfortunately, YAML lacks the language facilities to author components. Feel free to [skip ahead](/docs/iac/get-started/gcp/destroy-stack/). + +{{% /notes %}} + +{{% /choosable %}} + +### Deploy the component + +Now deploy the resulting component instantiation. To do so, run `pulumi up` as usual: + +``` +$ pulumi up +Previewing update (dev) + + Type Name Plan + pulumi:pulumi:Stack quickstart-dev + + ├─ quickstart:index:GcpStorageWebsite my-website create + + │ ├─ gcp:storage:Bucket my-bucket create + + │ ├─ gcp:storage:BucketObject index.html create + + │ └─ gcp:storage:BucketIAMBinding my-bucket-binding create + - ├─ gcp:storage:BucketIAMBinding my-bucket-binding delete + - ├─ gcp:storage:Bucket my-bucket delete + - └─ gcp:storage:BucketObject index.html delete + +Outputs: + - bucketName: "gs://my-bucket-a2b3c4d" + ~ url : "http://storage.googleapis.com/my-bucket-a2b3c4d/index.html-07d2e19" => "http://storage.googleapis.com/my-bucket-297424e/index.html" + +Resources: + + 4 to create + - 3 to delete + 7 changes. 1 unchanged + +Do you want to perform this update? [Use arrows to move, type to filter] + yes +> no + details +``` + +This preview shows you a few things. First, you'll see our `GcpStorageWebsite` component with all of its children resources neatly parented underneath it. This helps to see what resources relate to which components. Next, you'll see that your old resources are being destroyed. + +{{% notes type="info" %}} + +If you're wondering why Pulumi didn't simply update the resources in place, it's because certain changes -- like +refactoring resources into a component -- fundamentally change a resource's identity. Many changes like updating +properties or moving resources between files are not disruptive like this. It such cases, you can assign +[aliases](/docs/iac/concepts/options/aliases/) to prevent deletions from happening. + +{{% /notes %}} + +Accept the changes by selecting `yes` and the deployment will occur: + +``` +Updating (dev) + + Type Name Status + pulumi:pulumi:Stack quickstart-dev + + ├─ quickstart:index:GcpStorageWebsite my-website created (3s) + + │ ├─ gcp:storage:Bucket my-bucket created (1s) + + │ ├─ gcp:storage:BucketIAMBinding my-bucket-binding created (4s) + + │ └─ gcp:storage:BucketObject index.html created (0.74s) + - ├─ gcp:storage:BucketIAMBinding my-bucket-binding deleted (4s) + - ├─ gcp:storage:BucketObject index.html deleted (0.92s) + - └─ gcp:storage:Bucket my-bucket deleted (1s) + +Outputs: + - bucketName: "gs://my-bucket-a2b3c4d" + ~ url : "http://storage.googleapis.com/my-bucket-a2b3c4d/index.html-07d2e19" => "http://storage.googleapis.com/my-bucket-297424e/index.html" + +Resources: + + 4 created + - 3 deleted + 7 changes. 1 unchanged + +Duration: 10s +``` + +Now test out your new website -- it works like before, just with a tidier codebase now! + +{{% choosable os "linux,macos" %}} + +```bash +$ curl $(pulumi stack output url) + + +

Hello, Pulumi!

+ + +``` + +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> curl (pulumi stack output url) + + +

Hello, Pulumi!

+ + +``` + +{{% /choosable %}} + +Once you are ready to move on, let's destroy everything we've spun up in this tutorial. + +{{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/gcp/create-project.md b/content/docs/iac/get-started/gcp/create-project.md index fcc2e9de46ba..5b9a72d1615e 100644 --- a/content/docs/iac/get-started/gcp/create-project.md +++ b/content/docs/iac/get-started/gcp/create-project.md @@ -1,112 +1,190 @@ --- title_tag: Create a New Project | Google Cloud -meta_desc: This page provides an overview of how to create a new Google Cloud + Pulumi project. title: Create project -h1: "Pulumi & Google Cloud: Create project" -weight: 3 +h1: "Get started with Pulumi and Google Cloud" +meta_desc: This page provides an overview of how to create a new Google Cloud + Pulumi project. +weight: 4 menu: iac: name: Create project identifier: gcp-get-started.create-project parent: gcp-get-started - weight: 3 + weight: 4 + aliases: - /docs/quickstart/gcp/create-project/ - /docs/clouds/gcp/get-started/create-project/ + - /docs/quickstart/gcp/review-project/ + - /docs/get-started/gcp/review-project/ + - /docs/clouds/gcp/get-started/review-project/ --- -Now that you have set up your environment by installing Pulumi, installing your preferred language runtime, -and configuring your Google Cloud credentials, let's create your first Pulumi program. +## Create a new project + +A [**project**](/docs/iac/concepts/projects) is a program in your chosen language that defines a collection of related +cloud resources. In this step, you will create a new project. + +### Initializing your project + +Each project lives in its own directory. Create a new one: + +{{% choosable os "linux,macos" %}} + +```bash +$ mkdir quickstart +``` + +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> mkdir quickstart +``` + +{{% /choosable %}} + +Change into the new directory: -{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} +{{% choosable os "linux,macos" %}} + +```bash +$ cd quickstart +``` + +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> cd quickstart +``` + +{{% /choosable %}} + +Now initialize a new Pulumi project for Google Cloud using the `pulumi new` command: {{% choosable language typescript %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new gcp-typescript ``` +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi new gcp-typescript +``` + +{{% /choosable %}} + {{% /choosable %}} {{% choosable language python %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new gcp-python ``` +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi new gcp-python +``` + +{{% /choosable %}} + {{% /choosable %}} {{% choosable language go %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new gcp-go ``` +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi new gcp-go +``` + +{{% /choosable %}} + {{% /choosable %}} {{% choosable language csharp %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new gcp-csharp ``` +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi new gcp-csharp +``` + +{{% /choosable %}} + {{% /choosable %}} {{% choosable language java %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new gcp-java ``` {{% /choosable %}} +{{% choosable os "windows" %}} -{{% choosable language yaml %}} - -```bash -$ mkdir quickstart && cd quickstart -$ pulumi new gcp-yaml +```powershell +> pulumi new gcp-java ``` {{% /choosable %}} -The [`pulumi new`](/docs/cli/commands/pulumi_new) command creates a new Pulumi project with some basic scaffolding based on the cloud and language specified. - -{{< cli-note >}} +{{% /choosable %}} -After logging in, the CLI will proceed with walking you through creating a new project. +{{% choosable language yaml %}} -First, you will be asked for a **project name** and **project description**. Hit `ENTER` to accept the default values or specify new values. +{{% choosable os "linux,macos" %}} +```bash +$ pulumi new gcp-yaml ``` -This command will walk you through creating a new Pulumi project. -Enter a value or leave blank to accept the (default), and press . -Press ^C at any time to quit. +{{% /choosable %}} +{{% choosable os "windows" %}} -project name: (quickstart) -project description: (A minimal Google Cloud Pulumi program) -Created project 'quickstart' +```powershell +> pulumi new gcp-yaml ``` -Next, you will be asked for a **stack name**. Hit `ENTER` to accept the default value of `dev`. +{{% /choosable %}} -``` -Please enter your desired stack name. -To create a stack in an organization, use the format / (e.g. `acmecorp/dev`). -stack name: (dev) -Created stack 'dev' -``` +{{% /choosable %}} -Finally, you will be prompted for some configuration values for the stack. For Google Cloud projects, you will be prompted for the Google Cloud project. Enter your Google Cloud project ID at this prompt. +The `pulumi new` command interactively walks through initializing a new project, as well as creating a +[**stack**](/docs/iac/concepts/stacks) and [**configuring**](/docs/iac/concepts/config) it. A stack is an instance of your +project and you may have many of them -- like `dev`, `staging`, and `prod` -- each with different configuration settings. + +You will be prompted for configuration values such as a Google Cloud project ID. You can hit ENTER to accept the defaults, +or can type in your values: ``` -gcp:project: The Google Cloud project to deploy into: my-project -Saved config +gcp:project: The Google Cloud project to deploy into: my-gcp-project ``` -> What are [projects](/docs/concepts/projects/) and [stacks](/docs/concepts/stack/)? Pulumi projects and stacks let you organize Pulumi code. Consider a Pulumi _project_ to be analogous to a GitHub repo---a single place for code---and a _stack_ to be an instance of that code with a separate configuration. For instance, _Project Foo_ may have multiple stacks for different development environments (Dev, Test, or Prod), or perhaps for different cloud configurations (geographic region for example). See [Organizing Projects and Stacks](/docs/using-pulumi/organizing-projects-stacks/) for some best practices on organizing your Pulumi projects and stacks. +{{< cli-note >}} {{% choosable language "typescript" %}} @@ -144,6 +222,169 @@ After the command completes, the project and stack will be ready. {{% /choosable %}} -Next, we'll review the generated project files. +### Review your new project's contents + +If you list the contents of your directory, you'll see some key files: + +{{% choosable language java %}} + +- `src/main/java/myproject` is the project's Java package root + +{{% /choosable %}} + +{{% choosable language "typescript,python,go,csharp,java" %}} + +- {{< langfile >}} contains your project's main code that declares a Google Cloud Storage bucket + +- `Pulumi.yaml` is a [project file](/docs/iac/concepts/projects/project-file) containing metadata about your project like its name + +{{% /choosable %}} +{{% choosable language "yaml" %}} + +- `Pulumi.yaml` is a [project file](/docs/iac/concepts/projects/project-file) containing metadata about your project, like its name, as well as declaring your project's resources + +{{% /choosable %}} + +- `Pulumi.dev.yaml` contains configuration values for the stack you just initialized + +Now examine the code in {{< langfile >}}: + +{{% choosable language typescript %}} + +```typescript +import * as pulumi from "@pulumi/pulumi"; +import * as gcp from "@pulumi/gcp"; + +// Create a Google Cloud resource (Storage Bucket) +const bucket = new gcp.storage.Bucket("my-bucket", { + location: "US", +}); + +// Export the DNS name of the bucket +export const bucketName = bucket.url; +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +import pulumi +from pulumi_gcp import storage + +# Create a Google Cloud resource (Storage Bucket) +bucket = storage.Bucket("my-bucket", location="US") + +# Export the DNS name of the bucket +pulumi.export("bucket_name", bucket.url) +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +package main + +import ( + "github.com/pulumi/pulumi-gcp/sdk/v7/go/gcp/storage" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + // Create a Google Cloud resource (Storage Bucket) + bucket, err := storage.NewBucket(ctx, "my-bucket", &storage.BucketArgs{ + Location: pulumi.String("US"), + }) + if err != nil { + return err + } + + // Export the DNS name of the bucket + ctx.Export("bucketName", bucket.Url) + return nil + }) +} +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +using Pulumi; +using Pulumi.Gcp.Storage; +using System.Collections.Generic; + +return await Pulumi.Deployment.RunAsync(() => +{ + // Create a Google Cloud resource (Storage Bucket). + var bucket = new Bucket("my-bucket", new BucketArgs + { + Location = "US", + }); + + // Export the DNS name of the bucket. + return new Dictionary + { + ["bucketName"] = bucket.Url, + }; +}); +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +package myproject; + +import com.pulumi.Pulumi; +import com.pulumi.core.Output; +import com.pulumi.gcp.storage.Bucket; +import com.pulumi.gcp.storage.BucketArgs; + +public class App { + public static void main(String[] args) { + Pulumi.run(ctx -> { + // Create a Google Cloud resource (Storage Bucket) + var bucket = new Bucket("my-bucket", BucketArgs.builder() + .location("US") + .build()); + + // Export the DNS name of the bucket + ctx.export("bucketName", bucket.url()); + }); + } +} +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +```yaml +name: quickstart +runtime: yaml +description: A minimal Google Cloud Pulumi YAML program + +resources: + # Create a Google Cloud resource (Storage Bucket) + my-bucket: + type: gcp:storage:Bucket + properties: + location: US + +outputs: + # Export the DNS name of the bucket + bucketName: ${my-bucket.url} +``` + +{{% /choosable %}} + +This Pulumi program creates a new storage bucket resource and exports the DNS name of the bucket as a [stack output](/docs/iac/concepts/stacks/#outputs). Resources are just objects in our language of choice with [properties](/docs/iac/concepts/inputs-outputs) capturing their inputs and outputs. Exporting the bucket's ID makes it convenient to use afterwards. + +Next, you'll deploy your stack, which will provision your storage bucket. {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/gcp/deploy-changes.md b/content/docs/iac/get-started/gcp/deploy-changes.md deleted file mode 100644 index 220b5425a445..000000000000 --- a/content/docs/iac/get-started/gcp/deploy-changes.md +++ /dev/null @@ -1,419 +0,0 @@ ---- -title_tag: Deploy the Changes | Google Cloud -meta_desc: This page provides an overview of how deploy changes to a Google Cloud project. -title: Deploy changes -h1: "Pulumi & Google Cloud: Deploy changes" -weight: 7 -menu: - iac: - name: Deploy changes - identifier: gcp-get-started.deploy-changes - parent: gcp-get-started - weight: 7 - -aliases: - - /docs/quickstart/gcp/deploy-changes/ - - /docs/clouds/gcp/get-started/deploy-changes/ ---- - -Now let's deploy your changes. - -```bash -$ pulumi up -``` - -Pulumi will run the `preview` step of the update, which computes the minimally disruptive change to achieve the desired state described by the program: - -``` -Previewing update (dev) - - Type Name Plan - pulumi:pulumi:Stack quickstart-dev - + ├─ gcp:storage:BucketIAMBinding my-bucket-binding create - + └─ gcp:storage:BucketObject index.html create - -Resources: - + 2 to create - 2 unchanged - -Do you want to perform this update? -> yes - no - details -``` - -Choosing `yes` will proceed with the update and write the `index.html` file to the bucket: - -``` -Updating (dev) - - Type Name Status - pulumi:pulumi:Stack quickstart-dev - + ├─ gcp:storage:BucketIAMBinding my-bucket-binding created (5s) - + └─ gcp:storage:BucketObject index.html created (0.76s) - -Outputs: - bucketName: "gs://my-bucket-daa12be" - -Resources: - + 2 created - 2 unchanged - -Duration: 8s -``` - -Once the update has completed, you can verify the object was created by checking the Google Cloud Console or running the following `gsutil` command: - -{{< chooser language "typescript,python,go,csharp,java,yaml" >}} - -{{% choosable language typescript %}} - -```bash -$ gsutil ls $(pulumi stack output bucketName) -``` - -{{% /choosable %}} - -{{% choosable language python %}} - -```bash -$ gsutil ls $(pulumi stack output bucket_name) -``` - -{{% /choosable %}} - -{{% choosable language go %}} - -```bash -$ gsutil ls $(pulumi stack output bucketName) -``` - -{{% /choosable %}} - -{{% choosable language csharp %}} - -```bash -$ gsutil ls $(pulumi stack output bucketName) -``` - -{{% /choosable %}} - -{{% choosable language java %}} - -```bash -$ gsutil ls $(pulumi stack output bucketName) -``` - -{{% /choosable %}} - -{{% choosable language yaml %}} - -```bash -$ gsutil ls $(pulumi stack output bucketName) -``` - -{{% /choosable %}} - -{{< /chooser >}} - -Notice that your `index.html` file has been added to the bucket: - -```bash -gs://my-bucket-daa12be/index.html-a52debd -``` - -{{% choosable language typescript %}} - -Now that `index.html` exists in the bucket, modify the program to have the bucket serve the file as a static website. - -To do that, update the bucket definition to configure its `website` property. Then, to align with Google Cloud Storage recommendations, set its uniform bucket-level access property to `true`: - -```typescript -const bucket = new gcp.storage.Bucket("my-bucket", { - location: "US", - website: { - mainPageSuffix: "index.html" - }, - uniformBucketLevelAccess: true -}); -``` - -Finally, at the end of the file, export the website's public URL to make it easy to access: - -```typescript -export const bucketEndpoint = pulumi.concat("http://storage.googleapis.com/", bucket.name, "/", bucketObject.name); -``` - -{{% /choosable %}} - -{{% choosable language python %}} - -Now that `index.html` exists in the bucket, modify the program to have the bucket serve the file as a static website. - -To do that, update the bucket definition to configure its `website` property. Then, to align with Google Cloud Storage recommendations, set its uniform bucket-level access property to `True`: - -```python -bucket = storage.Bucket( - "my-bucket", - location="US", - website={ - "main_page_suffix": "index.html" - }, - uniform_bucket_level_access=True, -) -``` - -Finally, at the end of the file, export the website's public URL to make it easy to access: - -```python -pulumi.export( - "bucket_endpoint", - pulumi.Output.concat( - "http://storage.googleapis.com/", bucket.id, "/", bucket_object.name - ), -) -``` - -{{% /choosable %}} - -{{% choosable language go %}} - -Now that `index.html` exists in the bucket, modify the program to have the bucket serve the file as a static website. - -To do that, update the bucket definition to configure its `Website` property. Then, to align with Google Cloud Storage recommendations, set its uniform bucket-level access property to `true`: - -```go -bucket, err := storage.NewBucket(ctx, "my-bucket", &storage.BucketArgs{ - Location: pulumi.String("US"), - Website: storage.BucketWebsiteArgs{ - MainPageSuffix: pulumi.String("index.html"), - }, - UniformBucketLevelAccess: pulumi.Bool(true), -}) -if err != nil { - return err -} -``` - -Finally, at the end of the file, export the website's public URL to make it easy to access. - -```go -ctx.Export("bucketEndpoint", pulumi.Sprintf("http://storage.googleapis.com/%s/%s", bucket.Name, bucketObject.Name)) -``` - -Be sure to change the variable name of the `BucketObject` from `_` to `bucketObject` in this step, or Go may fail to compile the program: - -```go -bucketObject, err := storage.NewBucketObject(ctx, "index.html", &storage.BucketObjectArgs{ - Bucket: bucket.Name, - Source: pulumi.NewFileAsset("index.html"), -}) -``` - -{{% /choosable %}} - -{{% choosable language csharp %}} - -Now that `index.html` exists in the bucket, modify the program to have the bucket serve the file as a static website. - -To do that, update the bucket definition to configure its `Website` property. Then, to align with Google Cloud Storage recommendations, set its uniform bucket-level access property to `true`: - -```csharp -// Add this using statement -using Pulumi.Gcp.Storage.Inputs; -``` - -```csharp -var bucket = new Bucket("my-bucket", new BucketArgs -{ - Location = "US", - Website = new BucketWebsiteArgs - { - MainPageSuffix = "index.html" - }, - UniformBucketLevelAccess = true -}); -``` - -Finally, at the end of the file, export the website's public URL to make it easy to access: - -```csharp -return new Dictionary -{ - ["bucketName"] = bucket.Url, - ["bucketEndpoint"] = Output.Format($"http://storage.googleapis.com/{bucket.Name}/{bucketObject.Name}") -}; -``` - -{{% /choosable %}} - -{{% choosable language java %}} - -Now that `index.html` exists in the bucket, modify the program to have the bucket serve the file as a static website. - -To do that, add the `BucketWebsiteArgs` class to the list of imports, then update the bucket definition to configure its `website` property. To align with Google Cloud Storage recommendations, also set its uniform bucket-level access property to `true`: - -```java -// ... -import com.pulumi.gcp.storage.inputs.BucketWebsiteArgs; - -public class App { - public static void main(String[] args) { - Pulumi.run(ctx -> { - // Create an AWS resource (S3 Bucket) - var bucket = new Bucket("my-bucket", BucketArgs.builder() - .location("US") - .website(BucketWebsiteArgs.builder() - .mainPageSuffix("index.html") - .build()) - .build()); - //... -``` - -Finally, at the end of the file, export the website's public URL to make it easy to access: - -```java -ctx.export("bucketEndpoint", Output.format("http://storage.googleapis.com/%s/%s", bucket.name(), bucketObject.name())); -``` - -{{% /choosable %}} - -{{% choosable language yaml %}} - -Now that `index.html` exists in the bucket, modify the program to have the bucket serve the file as a static website. - -To do that, update the bucket definition to configure its `Website` property. Then, to align with Google Cloud Storage recommendations, set its uniform bucket-level access setting to `true`: - -```yaml -resources: - my-bucket: - type: gcp:storage:Bucket - properties: - location: US - website: - mainPageSuffix: index.html - uniformBucketLevelAccess: true -``` - -Finally, at the end of the file, export the website's public URL to make it easy to access: - -```yaml -# ... -outputs: - # ... - bucketEndpoint: http://storage.googleapis.com/${my-bucket.name}/${index-html.name} - ``` - -{{% /choosable %}} - -Give the stack one final update to apply these changes: - -```bash -$ pulumi up -``` - -Again, you'll see a preview of the changes to be made: - -``` -Previewing update (dev) - - Type Name Plan Info - pulumi:pulumi:Stack quickstart-dev - ~ └─ gcp:storage:Bucket my-bucket update [diff: +website~uniformBucketLevelAccess] - -Outputs: - + bucketEndpoint: "http://storage.googleapis.com/my-bucket-daa12be/index.html-a52debd" - -Resources: - ~ 1 to update - 3 unchanged - -Do you want to perform this update? -> yes - no - details -``` - -Choose `yes` to deploy them: - -``` -Updating (dev) - - Type Name Status Info - pulumi:pulumi:Stack quickstart-dev - ~ └─ gcp:storage:Bucket my-bucket updated (1s) [diff: +website~uniformBucketLevelAccess] - -Outputs: - + bucketEndpoint: "http://storage.googleapis.com/my-bucket-daa12be/index.html-a52debd" - bucketName : "gs://my-bucket-daa12be" - -Resources: - ~ 1 updated - 3 unchanged - -Duration: 4s -``` - -When the deployment completes, you can check out your new static website at the URL under `Outputs`, or make a `curl` request and see the contents of `index.html` printed to the terminal: - -{{% choosable language typescript %}} - -```bash -$ curl $(pulumi stack output bucketEndpoint) -``` - -{{% /choosable %}} - -{{% choosable language python %}} - -```bash -$ curl $(pulumi stack output bucket_endpoint) -``` - -{{% /choosable %}} - -{{% choosable language go %}} - -```bash -$ curl $(pulumi stack output bucketEndpoint) -``` - -{{% /choosable %}} - -{{% choosable language csharp %}} - -```bash -$ curl $(pulumi stack output bucketEndpoint) -``` - -{{% /choosable %}} - -{{% choosable language java %}} - -```bash -$ curl $(pulumi stack output bucketEndpoint) -``` - -{{% /choosable %}} - -{{% choosable language yaml %}} - -```bash -$ curl $(pulumi stack output bucketEndpoint) -``` - -{{% /choosable %}} - -And you should see: - -```bash - - -

Hello, Pulumi!

- - -``` - -Next you will destroy the resources. - -{{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/gcp/deploy-stack.md b/content/docs/iac/get-started/gcp/deploy-stack.md index f9279a9f6446..8c8d513fcef7 100644 --- a/content/docs/iac/get-started/gcp/deploy-stack.md +++ b/content/docs/iac/get-started/gcp/deploy-stack.md @@ -1,12 +1,12 @@ --- title_tag: Deploy the Stack | Google Cloud -meta_desc: This page provides an overview of how to deploy changes to a Google Cloud project. -title: Deploy stack -h1: "Pulumi & Google Cloud: Deploy stack" +title: Deploy to Google Cloud +h1: "Get started with Pulumi and Google Cloud" +meta_desc: Learn how to deploy your stack to a Google Cloud project in this guide. weight: 5 menu: iac: - name: Deploy stack + name: Deploy identifier: gcp-get-started.deploy-stack parent: gcp-get-started weight: 5 @@ -16,13 +16,15 @@ aliases: - /docs/clouds/gcp/get-started/deploy-stack/ --- -Let's go ahead and deploy your stack: +## Deploy to Google Cloud + +Now run `pulumi up` to start deploying your new storage bucket: ```bash $ pulumi up ``` -This command evaluates your program and determines the resource updates to make. First, a preview is shown that outlines the changes that will be made when you run the update: +This command first shows you a **preview** of the changes that will be made: ``` Previewing update (dev) @@ -32,21 +34,27 @@ Previewing update (dev) + └─ gcp:storage:Bucket my-bucket create Outputs: - bucketName: output + bucketName: [unknown] Resources: + 2 to create -Do you want to perform this update? [Use arrows to move, enter to select, type to filter] +Do you want to perform this update? > yes no details ``` -Once the preview has finished, you are given three options to choose from. Choosing `details` will show you a rich diff of the changes to be made. Choosing `yes` will create your new storage bucket in Google Cloud. Choosing `no` will return you to the user prompt without performing the update operation. +No changes have been made yet. You may decline to proceed by selecting `no` or choose `details` to +see more information about the proposed update like your bucket's properties. + +### Performing the update + +To proceed and deploy your new storage bucket, select `yes`. This begins an **update**: ``` -Updating (dev) +Do you want to perform this update? yes +Updating (dev): Type Name Status + pulumi:pulumi:Stack quickstart-dev created (3s) @@ -61,7 +69,14 @@ Resources: Duration: 4s ``` -Remember the output you defined in the previous step? That [stack output](/docs/concepts/stack#outputs) can be seen in the `Outputs:` section of your update. You can access your outputs from the CLI by running the `pulumi stack output [property-name]` command. For example you can print the name of your bucket with the following command: +Updates can take some time since they wait for the cloud resources to finish being created. Storage buckets +are quick, however, so the update will finish in just a few seconds. + +{{< auto-naming-note resource="bucket" suffix="daa12be" >}} + +### Using stack outputs + +The bucket name is available as a stack output. To view it: {{< chooser language "typescript,python,go,csharp,java,yaml" / >}} @@ -115,10 +130,14 @@ $ pulumi stack output bucketName Running that command will print out the name of your bucket. -{{< auto-naming-note resource="bucket" suffix="daa12be" >}} +### View your update on Pulumi Cloud + +If you are logged into [Pulumi Cloud](/docs/pulumi-cloud), you'll see "View Live" hyperlinks in the CLI output during your update. These go to [a page](https://app.pulumi.com) with detailed information about your stack including resources, configuration, a full history of updates, and more. Navigate to it to review the details of your update: -{{< console-note >}} + + A stack update with console output, as shown in the Pulumi Service + -Now that your bucket has been provisioned, let's modify the bucket to host a static website. +Now that the storage bucket has been provisioned, you'll update it to host a static website. {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/gcp/destroy-stack.md b/content/docs/iac/get-started/gcp/destroy-stack.md index 86fd709b9680..8bb1bfa816a2 100644 --- a/content/docs/iac/get-started/gcp/destroy-stack.md +++ b/content/docs/iac/get-started/gcp/destroy-stack.md @@ -1,33 +1,47 @@ --- title_tag: Destroy the Stack | Google Cloud -meta_desc: This page provides an overview of how to destroy a Pulumi stack of a Google Cloud (GCP) project. title: Destroy stack -h1: "Pulumi & Google Cloud: Destroy stack" +h1: "Get started with Pulumi and Google Cloud" +meta_desc: This page provides an overview of how to destroy a Pulumi stack of a Google Cloud project. weight: 8 menu: - iac: - name: Destroy stack - identifier: gcp-get-started.destroy-stack - parent: gcp-get-started - weight: 8 + iac: + name: Cleanup & destroy + identifier: gcp-get-started.destroy-stack + parent: gcp-get-started + weight: 8 aliases: - /docs/quickstart/gcp/destroy-stack/ - /docs/clouds/gcp/get-started/destroy-stack/ --- -Now that you've seen how to deploy and manage cloud resources with Pulumi, let's clean up by tearing down all of the resources that belong to this stack. +## Cleanup & destroy the stack -To do so, run the following: +Our final step is to clean up all of the resources we've allocated in this tutorial. + +Run the `pulumi destroy` command to delete all cloud resources in this project/stack: + +{{% choosable os "linux,macos" %}} ```bash $ pulumi destroy ``` -Again you'll be presented with a preview of the changes to be made. Choose `yes`. The destroy operation may take few moments, as Pulumi waits for all resources are to be deleted before considering the operation complete: +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> pulumi destroy +``` + +{{% /choosable %}} + +Just like `pulumi up`, you'll be shown a preview to ensure that you want to proceed: ``` -Previewing destroy (dev) +Previewing destroy (dev): Type Name Plan - pulumi:pulumi:Stack quickstart-dev delete @@ -36,24 +50,30 @@ Previewing destroy (dev) - └─ gcp:storage:Bucket my-bucket delete Outputs: - - bucketEndpoint: "http://storage.googleapis.com/my-bucket-daa12be/index.html-a52debd" - - bucketName : "gs://my-bucket-daa12be" + - url : "http://storage.googleapis.com/my-bucket-daa12be/index.html" Resources: - 4 to delete -Do you want to perform this destroy? yes -Destroying (dev) +Do you want to perform this destroy? +> yes + no + details +``` + +As with an update, we can choose `no` or `details`; select `yes` to proceed: + +``` +Destroying (dev): Type Name Status - - pulumi:pulumi:Stack quickstart-dev deleted + - pulumi:pulumi:Stack quickstart-dev deleted (0.31s) - ├─ gcp:storage:BucketIAMBinding my-bucket-binding deleted (6s) - ├─ gcp:storage:BucketObject index.html deleted (0.78s) - └─ gcp:storage:Bucket my-bucket deleted (1s) Outputs: - - bucketEndpoint: "http://storage.googleapis.com/my-bucket-daa12be/index.html-a52debd" - - bucketName : "gs://my-bucket-daa12be" + - url : "http://storage.googleapis.com/my-bucket-daa12be/index.html" Resources: - 4 deleted @@ -61,16 +81,29 @@ Resources: Duration: 9s ``` -Optionally, to delete the stack itself, you can also run [`pulumi stack rm`](/docs/cli/commands/pulumi_stack_rm). Doing so removes the stack entirely from the Pulumi Cloud, along with all of its update history. +At this stage, your stack still exists, but all cloud resources have been deleted from it. + +## Remove the stack + +The final step is to remove the stack itself. Destroy keeps the stack around so that you still have the full +history of what happened to the stack. Running [`pulumi stack rm`](/docs/cli/commands/pulumi_stack_rm) will +delete it entirely, including all history and state snapshots. Be careful, this step cannot be undone! -Congratulations! You've successfully provisioned some cloud resources using Pulumi. By completing this guide you have successfully: +{{% choosable "os" "macos,linux" %}} + +```bash +$ pulumi stack rm +``` + +{{% /choosable %}} +{{% choosable "os" "windows" %}} + +```powershell +> pulumi stack rm +``` -- Created a Pulumi new project. -- Provisioned a new storage bucket. -- Added an `index.html` file to your bucket. -- Served the file as a static website. -- Destroyed the resources you've provisioned. +{{% /choosable %}} -On the next page, we have a collection of examples and tutorials that you can deploy as they are or use them as a foundation for your own applications and infrastructure projects. +You'll be prompted to confirm the removal. Confirm it to successfully complete this tutorial. {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/gcp/modify-program.md b/content/docs/iac/get-started/gcp/modify-program.md index d4df8165d842..c463c8d661f8 100644 --- a/content/docs/iac/get-started/gcp/modify-program.md +++ b/content/docs/iac/get-started/gcp/modify-program.md @@ -1,12 +1,12 @@ --- -title_tag: Modify the Program | Google Cloud -meta_desc: This page provides an overview on how to update Google Cloud (GCP) project from a Pulumi program. -title: Modify program -h1: "Pulumi & Google Cloud: Modify program" +title_tag: Make an Update | Google Cloud +title: Make an update +h1: "Get started with Pulumi and Google Cloud" +meta_desc: This page provides an overview on how to update a Google Cloud project from a Pulumi program. weight: 6 menu: iac: - name: Modify program + name: Make an update identifier: gcp-get-started.modify-program parent: gcp-get-started weight: 6 @@ -14,9 +14,30 @@ menu: aliases: - /docs/quickstart/gcp/modify-program/ - /docs/clouds/gcp/get-started/modify-program/ + - /docs/quickstart/gcp/deploy-changes/ + - /docs/clouds/gcp/get-started/deploy-changes/ --- -Now that your storage bucket is provisioned, let's add an object to it. First, from within your project directory, create a new `index.html` file with some content in it. +## Make an update + +Now you will update your project to serve a static website out of your Google Cloud Storage bucket. You will change +your code and then re-run `pulumi up` which will update your infrastructure. + +### Add new resources + +Pulumi knows how to evolve your current infrastructure to your project's new desired state, both for +the first deployment as well as subsequent updates. + +To turn your bucket into a static website, you will add two new Google Cloud Storage resources: + +1. [`BucketObject`](/registry/packages/gcp/api-docs/storage/bucketobject/): + uploads your website content to the bucket +2. [`BucketIAMBinding`](/registry/packages/gcp/api-docs/storage/bucketiambinding/): + makes the bucket publicly accessible + +### Add an index.html + +First, from within your project directory, create a new `index.html` file with some content in it. {{< chooser os "macos,linux,windows" / >}} @@ -66,15 +87,15 @@ Now that you have an `index.html` file with some content, open {{< langfile >}} For this, you'll use Pulumi's `FileAsset` class to assign the content of the file to a new `BucketObject`. -{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} - {{% choosable language typescript %}} -In `index.ts`, create the `BucketObject` right after creating the bucket itself: +In `index.ts`, create the `BucketObject` by adding this code immediately following the bucket definition: ```typescript +// Upload the file const bucketObject = new gcp.storage.BucketObject("index.html", { bucket: bucket.name, + name: "index.html", source: new pulumi.asset.FileAsset("index.html") }); ``` @@ -83,11 +104,15 @@ const bucketObject = new gcp.storage.BucketObject("index.html", { {{% choosable language python %}} -In `__main__.py`, create a new bucket object by adding the following right after creating the bucket itself: +In `__main__.py`, create a new bucket object by adding this code immediately following the bucket definition: ```python +# Upload the file bucket_object = storage.BucketObject( - "index.html", bucket=bucket.name, source=pulumi.FileAsset("index.html") + "index.html", + bucket=bucket.name, + name="index.html", + source=pulumi.FileAsset("index.html") ) ``` @@ -95,11 +120,13 @@ bucket_object = storage.BucketObject( {{% choosable language go %}} -In `main.go`, create the `BucketObject` right after creating the bucket itself: +In `main.go`, create the `BucketObject` by adding this code immediately following the bucket definition: ```go +// Upload the file _, err = storage.NewBucketObject(ctx, "index.html", &storage.BucketObjectArgs{ Bucket: bucket.Name, + Name: pulumi.String("index.html"), Source: pulumi.NewFileAsset("index.html"), }) if err != nil { @@ -111,12 +138,14 @@ if err != nil { {{% choosable language csharp %}} -In `Program.cs`, create the `BucketObject` right after creating the bucket itself: +In `Program.cs`, create the `BucketObject` by adding this code immediately following the bucket definition: ```csharp +// Upload the file var bucketObject = new BucketObject("index.html", new BucketObjectArgs { Bucket = bucket.Name, + Name = "index.html", Source = new FileAsset("./index.html") }); ``` @@ -125,7 +154,7 @@ var bucketObject = new BucketObject("index.html", new BucketObjectArgs {{% choosable language java %}} -In {{< langfile >}}, import the following additional classes, then create the `BucketObject` right after creating the bucket itself: +In {{< langfile >}}, import the following additional classes, then create the `BucketObject` immediately following the bucket definition by adding this code: ```java // ... @@ -138,11 +167,15 @@ import com.pulumi.gcp.storage.BucketObjectArgs; public class App { public static void main(String[] args) { Pulumi.run(ctx -> { - // ... + // Create a Google Cloud resource (Storage Bucket) + var bucket = new Bucket("my-bucket", BucketArgs.builder() + // existing bucket configuration + .build()); - // Create a Bucket object + // Upload the file var bucketObject = new BucketObject("index.html", BucketObjectArgs.builder() .bucket(bucket.name()) + .name("index.html") .source(new FileAsset("index.html")) .build() ); @@ -160,14 +193,14 @@ public class App { In {{< langfile >}}, create the `BucketObject` right below the bucket itself. ```yaml -resources: - # ... - index-html: +# Upload the file +index-html: type: gcp:storage:BucketObject properties: - bucket: ${my-bucket} - source: - fn::fileAsset: ./index.html + bucket: ${my-bucket} + name: index.html + source: + fn::fileAsset: ./index.html ``` {{% /choosable %}} @@ -260,6 +293,266 @@ my-bucket-binding: {{% /choosable %}} -Next, you'll deploy your changes. +{{% notes type="info" %}} + +If you encounter permission errors when uploading files, the IAM binding may still be propagating. The component +examples later in this tutorial show how to add explicit dependencies between resources to ensure proper ordering. + +{{% /notes %}} + +Now that `index.html` exists in the bucket, modify the bucket to serve the file as a static website. + +To do that, update the bucket definition to configure its website property. Then, to align with Google Cloud Storage recommendations, set its uniform bucket-level access property to `true`: + +{{% choosable language typescript %}} + +```typescript +const bucket = new gcp.storage.Bucket("my-bucket", { + location: "US", + website: { + mainPageSuffix: "index.html" + }, + uniformBucketLevelAccess: true +}); +``` + +### Export the website URL + +Now to export the website's public URL for easy access, add the `url` export as shown in this example: + +```typescript +// Export the DNS name of the bucket +export const bucketName = bucket.url; + +// Export the bucket's public URL +export const url = pulumi.concat("http://storage.googleapis.com/", bucket.name, "/", bucketObject.name); +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +bucket = storage.Bucket( + "my-bucket", + location="US", + website={ + "main_page_suffix": "index.html" + }, + uniform_bucket_level_access=True, +) +``` + +### Export the website URL + +Now to export the website's public URL for easy access, add the `url` export as shown in this example: + +```python +# Export the DNS name of the bucket +pulumi.export("bucket_name", bucket.url) + +# Export the bucket's public URL +pulumi.export( + "url", + pulumi.Output.concat( + "http://storage.googleapis.com/", bucket.id, "/", bucket_object.name + ), +) +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +bucket, err := storage.NewBucket(ctx, "my-bucket", &storage.BucketArgs{ + Location: pulumi.String("US"), + Website: storage.BucketWebsiteArgs{ + MainPageSuffix: pulumi.String("index.html"), + }, + UniformBucketLevelAccess: pulumi.Bool(true), +}) +if err != nil { + return err +} +``` + +### Export the website URL + +Now to export the website's public URL for easy access, add the `url` export as shown in this example: + +```go +// Export the DNS name of the bucket +ctx.Export("bucketName", bucket.Url) + +// Export the bucket's public URL +ctx.Export("url", pulumi.Sprintf("http://storage.googleapis.com/%s/%s", bucket.Name, bucketObject.Name)) +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +var bucket = new Bucket("my-bucket", new BucketArgs +{ + Location = "US", + Website = new Pulumi.Gcp.Storage.Inputs.BucketWebsiteArgs + { + MainPageSuffix = "index.html" + }, + UniformBucketLevelAccess = true +}); +``` + +### Export the website URL + +Now to export the website's public URL for easy access, add the `url` export to your return `Dictionary` as shown in this example: + +```csharp +return new Dictionary +{ + ["bucketName"] = bucket.Url, + ["url"] = Output.Format($"http://storage.googleapis.com/{bucket.Name}/{bucketObject.Name}") +}; +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +var bucket = new Bucket("my-bucket", BucketArgs.builder() + .location("US") + .website(BucketWebsiteArgs.builder() + .mainPageSuffix("index.html") + .build()) + .uniformBucketLevelAccess(true) + .build()); +``` + +### Export the website URL + +Now to export the website's public URL for easy access, add the `url` export as shown in this example: + +```java +// Export the DNS name of the bucket +ctx.export("bucketName", bucket.url()); + +// Export the bucket's public URL +ctx.export("url", Output.format("http://storage.googleapis.com/%s/%s", bucket.name(), bucketObject.name())); +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +```yaml +resources: + my-bucket: + type: gcp:storage:Bucket + properties: + location: US + website: + mainPageSuffix: index.html + uniformBucketLevelAccess: true + +outputs: + bucketName: ${my-bucket.url} + url: http://storage.googleapis.com/${my-bucket.name}/${index-html.name} +``` + +{{% /choosable %}} + +We prepend `http://` using a helper because the bucket's URL is [an output property](/docs/iac/concepts/inputs-outputs/#outputs) +that Google Cloud assigns at deployment time, not a raw string, meaning its value is not known in advance. + +### Deploy the changes + +To deploy the changes, run `pulumi up` again and it will figure out the deltas: + +{{% choosable os "linux,macos" %}} + +```bash +$ pulumi up +``` + +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi up +``` + +{{% /choosable %}} + +Just like the first time you will see a preview of the changes before they happen: + +``` +Previewing update (dev): + + Type Name Plan + pulumi:pulumi:Stack quickstart-dev + + ├─ gcp:storage:BucketObject index.html create + + └─ gcp:storage:BucketIAMBinding my-bucket-binding create + ~ └─ gcp:storage:Bucket my-bucket update + +Outputs: + + url: "http://storage.googleapis.com/my-bucket-a2b3c4d/index.html" + +Resources: + + 2 to create + ~ 1 to update + 3 changes. 1 unchanged + +Do you want to perform this update? +> yes + no + details +``` + +Choose `yes` to perform the deployment: + +``` +Do you want to perform this update? yes +Updating (dev): + + Type Name Status + pulumi:pulumi:Stack quickstart-dev + + ├─ gcp:storage:BucketObject index.html created + + └─ gcp:storage:BucketIAMBinding my-bucket-binding created + ~ └─ gcp:storage:Bucket my-bucket updated + +Outputs: + bucketName: "gs://my-bucket-a2b3c4d" + + url : "http://storage.googleapis.com/my-bucket-a2b3c4d/index.html" + +Resources: + + 2 created + ~ 1 updated + 2 unchanged + +Duration: 8s +``` + +In just a few seconds, your new website will be ready. Curl the endpoint to see it live: + +```bash +$ curl $(pulumi stack output url) +``` + +This will reveal your new website! + +``` + + +

Hello, Pulumi!

+ + +``` + +Feel free to experiment, such as changing the contents of `index.html` and redeploying. + +Next, wrap the website into an infrastructure abstraction. {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/gcp/next-steps.md b/content/docs/iac/get-started/gcp/next-steps.md index a8511b016b86..38eef7c44e98 100644 --- a/content/docs/iac/get-started/gcp/next-steps.md +++ b/content/docs/iac/get-started/gcp/next-steps.md @@ -1,23 +1,32 @@ --- title_tag: Next Steps | Google Cloud +title: Next steps +h1: Next Steps with Pulumi & Google Cloud +stepper_link: "Congratulations!" meta_desc: This page provides a list of tutorials that take a deeper dive into Google Cloud cloud resources. -title: Next steps -h1: "Pulumi & Google Cloud: Next steps" weight: 9 menu: - iac: - name: Next steps - identifier: gcp-get-started.next-steps - parent: gcp-get-started - weight: 9 + iac: + name: Next steps + identifier: gcp-get-started.next-steps + parent: gcp-get-started + weight: 9 aliases: - /docs/quickstart/gcp/next-steps/ - /docs/clouds/gcp/get-started/next-steps/ --- -Congrats! You've deployed your first project on Google Cloud with Pulumi. Here are some next steps, depending on your learning style. +Congratulations! You've successfully provisioned some cloud resources using Pulumi. By completing this guide you have successfully: + +- Created a Pulumi new project. +- Provisioned a new storage bucket. +- Turned it into a static website. +- Created a website component for easy reuse. +- Destroyed all of the resources you've provisioned. + +Below are some recommended next steps, including examples and tutorials that you can explore or use them as a foundation for your own applications and infrastructure projects. Also be sure to [join the Community Slack](https://slack.pulumi.com/) to meet fellow IaC practitioners. ## Try Pulumi ESC (Environments, Secrets, and Configuration) @@ -31,11 +40,11 @@ With Pulumi ESC you can: {{< get-started-next-step path="/docs/esc/get-started" label="Learn more about Pulumi ESC" ref="gs-gcp-esc" >}} -## Learn Pulumi +## Try a tutorial -Dive into Learn Pulumi for a comprehensive walkthrough of key Pulumi concepts in the context of a real-life application. +Let our Google Cloud tutorials guide you through key Pulumi concepts. -{{< get-started-next-step path="/learn/pulumi-fundamentals" label="Learn Pulumi Fundamentals" ref="gs-gcp-learn" >}} +{{< get-started-next-step path="/docs/using-pulumi/" label="Browse tutorials" ref="gs-gcp-tutorials" >}} ## Launch a new project with a template diff --git a/content/docs/iac/get-started/gcp/review-project.md b/content/docs/iac/get-started/gcp/review-project.md deleted file mode 100644 index 49792433dd3a..000000000000 --- a/content/docs/iac/get-started/gcp/review-project.md +++ /dev/null @@ -1,247 +0,0 @@ ---- -title_tag: Review the New Project | Google Cloud -meta_desc: This page provides an overview on how to review a new Google Cloud project. -title: Review project -h1: "Pulumi & Google Cloud: Review project" -weight: 4 -menu: - iac: - name: Review project - identifier: gcp-get-started.review-project - parent: gcp-get-started - weight: 4 - -aliases: - - /docs/quickstart/gcp/review-project/ - - /docs/clouds/gcp/get-started/review-project/ ---- - -Let's review some of the generated project files: - -{{% choosable language "typescript,python,go,csharp,java" %}} - -- `Pulumi.yaml` defines the [project](/docs/concepts/projects/). - -{{% /choosable %}} - -{{% choosable language yaml %}} - -- `Pulumi.yaml` defines both the [project](/docs/concepts/projects/) and the program that manages your stack resources. - -{{% /choosable %}} - -- `Pulumi.dev.yaml` contains [configuration](/docs/concepts/config/) values for the [stack](/docs/concepts/stack/) you initialized. - -{{% choosable language java %}} - -- `src/main/java/myproject` defines the project's Java package root. - -{{% /choosable %}} - -{{% choosable language python %}} - -- `__main__.py` is the Pulumi program that defines your stack resources. - -{{% /choosable %}} - -{{% choosable language "typescript,go,csharp,java" %}} - -- {{< langfile >}} is the Pulumi program that defines your stack resources. - -{{% /choosable %}} - -Let's examine {{< langfile >}}. - -{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} - -{{% choosable language typescript %}} - -```typescript -import * as pulumi from "@pulumi/pulumi"; -import * as gcp from "@pulumi/gcp"; - -// Create a Google Cloud resource (Storage Bucket) -const bucket = new gcp.storage.Bucket("my-bucket", { - location: "US", -}); - -// Export the DNS name of the bucket -export const bucketName = bucket.url; -``` - -{{% /choosable %}} - -{{% choosable language python %}} - -```python -import pulumi -from pulumi_gcp import storage - -# Create a Google Cloud resource (Storage Bucket) -bucket = storage.Bucket("my-bucket", location="US") - -# Export the DNS name of the bucket -pulumi.export("bucket_name", bucket.url) -``` - -{{% /choosable %}} - -{{% choosable language go %}} - -```go -package main - -import ( - "github.com/pulumi/pulumi-gcp/sdk/v7/go/gcp/storage" - "github.com/pulumi/pulumi/sdk/v3/go/pulumi" -) - -func main() { - pulumi.Run(func(ctx *pulumi.Context) error { - // Create a Google Cloud resource (Storage Bucket) - bucket, err := storage.NewBucket(ctx, "my-bucket", &storage.BucketArgs{ - Location: pulumi.String("US"), - }) - if err != nil { - return err - } - - // Export the DNS name of the bucket - ctx.Export("bucketName", bucket.Url) - return nil - }) -} -``` - -{{% /choosable %}} - -{{% choosable language csharp %}} - -```csharp -using Pulumi; -using Pulumi.Gcp.Storage; -using System.Collections.Generic; - -return await Deployment.RunAsync(() => -{ - // Create a Google Cloud resource (Storage Bucket). - var bucket = new Bucket("my-bucket", new BucketArgs - { - Location = "US", - }); - - // Export the DNS name of the bucket. - return new Dictionary - { - ["bucketName"] = bucket.Url, - }; -}); -``` - -{{% /choosable %}} - -{{% choosable language java %}} - -```java -package myproject; - -import com.pulumi.Pulumi; -import com.pulumi.core.Output; -import com.pulumi.gcp.storage.Bucket; -import com.pulumi.gcp.storage.BucketArgs; - -public class App { - public static void main(String[] args) { - Pulumi.run(ctx -> { - // Create a Google Cloud resource (Storage Bucket) - var bucket = new Bucket("my-bucket", BucketArgs.builder() - .location("US") - .build()); - - // Export the DNS name of the bucket - ctx.export("bucketName", bucket.url()); - }); - } -} -``` - -{{% /choosable %}} - -{{% choosable language yaml %}} - -```yaml -name: quickstart -runtime: yaml -description: A minimal Google Cloud Pulumi YAML program - -resources: - # Create a Google Cloud resource (Storage Bucket) - my-bucket: - type: gcp:storage:Bucket - properties: - location: US - -outputs: - # Export the DNS name of the bucket - bucketName: ${my-bucket.url} -``` - -{{% /choosable %}} - -This Pulumi program creates a new storage bucket and exports the DNS name of the bucket. - -{{% choosable language typescript %}} - -```typescript -export const bucketName = bucket.url; -``` - -{{% /choosable %}} - -{{% choosable language python %}} - -```python -pulumi.export("bucket_name", bucket.url) -``` - -{{% /choosable %}} - -{{% choosable language go %}} - -```go -ctx.Export("bucketName", bucket.Url) -``` - -{{% /choosable %}} - -{{% choosable language csharp %}} - -```csharp -return new Dictionary -{ - ["bucketName"] = bucket.Url, -}; -``` - -{{% /choosable %}} - -{{% choosable language java %}} - -```java -ctx.export("bucketName", bucket.url()); -``` - -{{% /choosable %}} - -{{% choosable language yaml %}} - -```yaml -outputs: - bucketName: ${my-bucket.url} -``` - -{{% /choosable %}} - -Next, you'll deploy your stack, which will provision your storage bucket. - -{{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/kubernetes/_index.md b/content/docs/iac/get-started/kubernetes/_index.md index 1062307f4a4d..624cf0a90451 100644 --- a/content/docs/iac/get-started/kubernetes/_index.md +++ b/content/docs/iac/get-started/kubernetes/_index.md @@ -17,24 +17,68 @@ aliases: - /docs/get-started/kubernetes/ - /docs/quickstart/kubernetes/ - /docs/clouds/kubernetes/get-started/ + - /docs/iac/get-started/kubernetes/deploy-changes/ + - /docs/iac/get-started/kubernetes/review-project/ --- -Pulumi's Cloud Native SDK makes it easy to target any Kubernetes environment to -provision a cluster, configure and deploy applications, and update them as -required. +**Infrastructure as code (IaC)** lets you deploy, change, and manage infrastructure safely, consistently, +and repeatably using code rather than a graphical user interface. -Pulumi supports programming against Kubernetes---Minikube, on-premises and -cloud-hosted custom Kubernetes clusters, and the managed services from Google -(GKE), Azure (AKS), and Amazon (EKS). The Pulumi Kubernetes provider -packages and CLI help you accomplish all these within minutes. +Complete this step-by-step tutorial to deploy an [NGINX](https://www.nginx.com/) web server on Kubernetes using IaC. -For a quick example of how Pulumi deploys infrastructure on Kubernetes, this tutorial takes you through the following steps to easily deploy an [NGINX](https://www.nginx.com/) web server: +## Before you begin -1. Setting up and configuring Pulumi to access your Kubernetes cluster. -1. Creating a new Pulumi project. -1. Deploying NGINX on Kubernetes. -1. Creating a service to access the NGINX deployment. -1. Cleaning up your deployment by destroying the resources you've provisioned. +You need access to a Kubernetes cluster (local or cloud-based) and kubectl installed and configured. Choose your language and ensure you've performed any prerequisites: + +{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} + +{{% choosable language "typescript" %}} + +* Access to a Kubernetes cluster (local or cloud-based) +* kubectl installed and configured +* Node.js and npm installed locally + +{{% /choosable %}} + +{{% choosable language "python" %}} + +* Access to a Kubernetes cluster (local or cloud-based) +* kubectl installed and configured +* Python and pip, Poetry or uv installed locally + +{{% /choosable %}} + +{{% choosable language "go" %}} + +* Access to a Kubernetes cluster (local or cloud-based) +* kubectl installed and configured +* Go installed locally + +{{% /choosable %}} + +{{% choosable language "csharp" %}} + +* Access to a Kubernetes cluster (local or cloud-based) +* kubectl installed and configured +* .NET installed locally + +{{% /choosable %}} + +{{% choosable language "java" %}} + +* Access to a Kubernetes cluster (local or cloud-based) +* kubectl installed and configured +* Java 11+ and Maven 3.6.1+ installed locally + +{{% /choosable %}} + +{{% choosable language "yaml" %}} + +* Access to a Kubernetes cluster (local or cloud-based) +* kubectl installed and configured +* A text editor + +{{% /choosable %}} Before you begin, watch this overview of how to deploy Kubernetes infrastructure with Pulumi: diff --git a/content/docs/iac/get-started/kubernetes/begin.md b/content/docs/iac/get-started/kubernetes/begin.md index 4bb642b595f7..fc4eea99b278 100644 --- a/content/docs/iac/get-started/kubernetes/begin.md +++ b/content/docs/iac/get-started/kubernetes/begin.md @@ -1,8 +1,9 @@ --- -title_tag: Before You Begin | Kubernetes -meta_desc: This page provides an overview on how to get started with Pulumi when starting an Kubernetes project. -title: Before you begin -h1: "Pulumi & Kubernetes: Before you begin" +title_tag: Install Pulumi | Kubernetes +meta_desc: This page provides an overview of how to get started with Pulumi when starting a Kubernetes project. +title: Install Pulumi +h1: "Get started with Pulumi and Kubernetes" +stepper_link: "I'm ready to begin" weight: 2 menu: iac: @@ -19,54 +20,6 @@ aliases: - /get-started/kubernetes/begin/ --- -Before we get started using Pulumi, let's run through a few quick steps to ensure our environment is setup correctly. - -### Install Pulumi - -{{< install-pulumi >}} -{{% notes "info" %}} -All Windows examples in this tutorial assume you are running in PowerShell. -{{% /notes %}} -{{< /install-pulumi >}} - -Next, we'll install the required language runtime. - -### Install Language Runtime - -#### Choose Your Language - -{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} - -{{% choosable language "typescript" %}} -{{< install-node >}} -{{% /choosable %}} - -{{% choosable language python %}} -{{< install-python >}} -{{% /choosable %}} - -{{% choosable language go %}} -{{< install-go >}} -{{% /choosable %}} - -{{% choosable language "csharp,fsharp,visualbasic" %}} -{{< install-dotnet >}} -{{% /choosable %}} - -{{% choosable language java %}} -{{< install-java >}} -{{% /choosable %}} - -{{% choosable language yaml %}} -{{< install-yaml >}} -{{% /choosable %}} - -Next, we'll configure Kubernetes. - -### Configure Kubernetes - -Configure Kubernetes so the Pulumi CLI can connect to a Kubernetes cluster. If you have previously configured the kubectl CLI, `kubectl`, Pulumi will respect and use your configuration settings. Depending on the approach you choose, you may need to apply some of the configuration after creating your project and stack in the next step. - -Next, we'll create a new Pulumi project. +{{< get-started-install-body >}} {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/kubernetes/configure.md b/content/docs/iac/get-started/kubernetes/configure.md new file mode 100644 index 000000000000..175e7d88e03d --- /dev/null +++ b/content/docs/iac/get-started/kubernetes/configure.md @@ -0,0 +1,136 @@ +--- +title_tag: Configure access | Kubernetes +title: Configure access +h1: "Get started with Pulumi and Kubernetes" +meta_desc: This page provides an overview on how to get started with Pulumi when starting a Kubernetes project. +weight: 3 +menu: + iac: + name: Configure access + parent: kubernetes-get-started + weight: 3 + identifier: kubernetes-get-started.configure +aliases: + - /docs/quickstart/kubernetes/configure/ + - /docs/get-started/kubernetes/configure/ + - /docs/clouds/kubernetes/get-started/configure/ +--- + +## Configure access to Kubernetes + +Pulumi's CLI needs access to a Kubernetes cluster to manage cloud resources. + +You must have access to a Kubernetes cluster—either a local cluster (such as Minikube, kind, or Docker Desktop) or a cloud-managed cluster (such as GKE, AKS, or EKS). + +You also need kubectl installed and configured to access your cluster. + +### Testing access + +To test that your Kubernetes cluster access is configured properly, run: + +{{% choosable os "linux,macos" %}} + +```bash +$ kubectl cluster-info +``` + +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> kubectl cluster-info +``` + +{{% /choosable %}} + +If your cluster's control plane and services are printed, your configuration is correct. If not, read on: + +``` +Kubernetes control plane is running at https://127.0.0.1:6443 +CoreDNS is running at https://127.0.0.1:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy +``` + +You can also verify you have access to cluster resources: + +{{% choosable os "linux,macos" %}} + +```bash +$ kubectl get nodes +$ kubectl auth can-i get pods +``` + +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> kubectl get nodes +> kubectl auth can-i get pods +``` + +{{% /choosable %}} + +### How Pulumi accesses your cluster + +Pulumi uses the same kubeconfig file that kubectl uses (typically `~/.kube/config`). If you can run `kubectl get nodes`, Pulumi will work automatically. + +You can verify which context Pulumi will use: + +{{% choosable os "linux,macos" %}} + +```bash +$ kubectl config current-context +``` + +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> kubectl config current-context +``` + +{{% /choosable %}} + +### Alternative approaches + +If you need to use a specific kubeconfig file or context, you can set: + +{{% choosable os "linux,macos" %}} + +```bash +$ export KUBECONFIG="$HOME/path/to/kubeconfig" +``` + +{{% /choosable %}} + +{{% choosable os windows %}} + +```powershell +> $env:KUBECONFIG = "C:\path\to\kubeconfig" +``` + +{{% /choosable %}} + +Or specify the context in your Pulumi stack configuration: + +{{% choosable os "linux,macos" %}} + +```bash +$ pulumi config set kubernetes:context my-cluster-context +``` + +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> pulumi config set kubernetes:context my-cluster-context +``` + +{{% /choosable %}} + +For detailed information on Pulumi's use of Kubernetes credentials, see [Kubernetes Setup](/registry/packages/kubernetes/installation-configuration/). + +{{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/kubernetes/create-component.md b/content/docs/iac/get-started/kubernetes/create-component.md new file mode 100644 index 000000000000..e5649ba87e5e --- /dev/null +++ b/content/docs/iac/get-started/kubernetes/create-component.md @@ -0,0 +1,988 @@ +--- +title_tag: Create a component | Kubernetes +title: Create a component +h1: "Get started with Pulumi and Kubernetes" +meta_desc: This page provides an overview on how to create infrastructure abstractions with Pulumi. +weight: 7 +menu: + iac: + name: Create a component + identifier: kubernetes-get-started.create-component + parent: kubernetes-get-started + weight: 7 + +aliases: + - /docs/quickstart/kubernetes/create-component/ +--- + +## Create a component + +[**Components**](/docs/iac/concepts/resources/components/) are infrastructure abstractions that encapsulate +complexity and enable sharing and reuse. Instead of copy-pasting common patterns, you can encode them as components. + +You will now create your first component that packages up your Kubernetes NGINX deployment so you can easily stamp out +entire NGINX services in just a few lines of code: + +{{% choosable language typescript %}} + +```typescript +const nginx = new KubernetesNginxService("my-nginx", { + isMinikube: config.requireBoolean("isMinikube") +}); +export const ip = nginx.ip; +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +nginx = KubernetesNginxService('my-nginx', is_minikube=config.require_bool("isMinikube")) +pulumi.export("ip", nginx.ip) +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +nginx, err := NewKubernetesNginxService(ctx, "my-nginx", KubernetesNginxServiceArgs{ + IsMinikube: config.GetBool(ctx, "isMinikube"), +}) +if err != nil { + return err +} +ctx.Export("ip", nginx.Ip) +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +var nginx = new KubernetesNginxService("my-nginx", new KubernetesNginxServiceArgs() +{ + IsMinikube = config.GetBoolean("isMinikube") ?? false +}); + +return new Dictionary +{ + ["ip"] = nginx.Ip +}; +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +var nginx = new KubernetesNginxService("my-nginx", + new KubernetesNginxServiceArgs(config.requireBoolean("isMinikube"))); +ctx.export("ip", nginx.ip); +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +{{% notes type="warning" %}} + +Unfortunately, YAML lacks the language facilities to author components. Feel free to [skip ahead](/docs/iac/get-started/kubernetes/destroy-stack/). + +{{% /notes %}} + +{{% /choosable %}} + +Using components here also has the benefit that, as the requirements for your NGINX service change, you can +update the one component definition and have all uses of it benefit. + +### Define a new component + +To define a new component, create a class called `KubernetesNginxService` that derives from `ComponentResource`. It'll have a mostly-empty +constructor to start with but you will add the Kubernetes resources to it in the next step. You'll also define the inputs for the +component -- the `isMinikube` flag to determine service type -- and outputs -- a single property with the service `ip`. + +To get going, create a new file {{< compfile >}} alongside {{< langfile >}} and add the following: + +{{% choosable language typescript %}} + +```typescript +import * as pulumi from "@pulumi/pulumi"; +import * as k8s from "@pulumi/kubernetes"; + +// Arguments for the Kubernetes NGINX service component. +export interface KubernetesNginxServiceArgs { + isMinikube: boolean; // whether running on minikube. +} + +// A component that encapsulates creating a Kubernetes NGINX deployment and service. +export class KubernetesNginxService extends pulumi.ComponentResource { + public readonly ip: pulumi.Output; // the service ip. + + constructor(name: string, args: KubernetesNginxServiceArgs, opts?: pulumi.ComponentResourceOptions) { + super("quickstart:index:KubernetesNginxService", name, args, opts); + + // Component initialization will go here next... + + this.registerOutputs({}); // Signal component completion. + } +} +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +import pulumi +from pulumi_kubernetes.apps.v1 import Deployment +from pulumi_kubernetes.core.v1 import Service + +# A component that encapsulates creating a Kubernetes NGINX deployment and service. +class KubernetesNginxService(pulumi.ComponentResource): + def __init__(self, name: str, is_minikube: bool = False, opts = None): + super().__init__('quickstart:index:KubernetesNginxService', name, { 'isMinikube': is_minikube }, opts) + + # Component initialization will go here next... + + self.register_outputs({}) # Signal component completion. +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +package main + +import ( + appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1" + corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1" + metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type KubernetesNginxService struct { + pulumi.ResourceState + Ip pulumi.StringOutput // the service ip. +} + +type KubernetesNginxServiceArgs struct { + IsMinikube bool // whether running on minikube. +} + +func NewKubernetesNginxService(ctx *pulumi.Context, name string, args KubernetesNginxServiceArgs, opts ...pulumi.ResourceOption) (*KubernetesNginxService, error) { + self := &KubernetesNginxService{} + err := ctx.RegisterComponentResource("quickstart:index:KubernetesNginxService", name, self, opts...) + if err != nil { + return nil, err + } + + // Component initialization will go here next... + + ctx.RegisterResourceOutputs(self, pulumi.Map{}) // Signal component completion. + return self, nil +} +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +using Pulumi; +using Pulumi.Kubernetes.Core.V1; +using Pulumi.Kubernetes.Types.Inputs.Core.V1; +using Pulumi.Kubernetes.Types.Inputs.Apps.V1; +using Pulumi.Kubernetes.Types.Inputs.Meta.V1; +using System.Collections.Generic; + +public class KubernetesNginxServiceArgs +{ + public bool IsMinikube { get; set; } +} + +public class KubernetesNginxService : Pulumi.ComponentResource +{ + public Output Ip { get; private set; } + + public KubernetesNginxService(string name, KubernetesNginxServiceArgs args, ComponentResourceOptions? opts = null) + : base("quickstart:index:KubernetesNginxService", name, opts) + { + // Component initialization will go here next... + + this.RegisterOutputs(new Dictionary{}); // Signal component completion. + } +} +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +package myproject; + +import com.pulumi.resources.ComponentResource; +import com.pulumi.resources.ComponentResourceOptions; +import com.pulumi.core.Output; + +public class KubernetesNginxServiceArgs { + public boolean isMinikube; + public KubernetesNginxServiceArgs(boolean isMinikube) { + this.isMinikube = isMinikube; + } +} + +public class KubernetesNginxService extends ComponentResource { + public Output ip; + + public KubernetesNginxService(String name, KubernetesNginxServiceArgs args, ComponentResourceOptions opts) { + super("quickstart:index:KubernetesNginxService", name, args, opts); + + // Component initialization will go here next... + + this.registerOutputs(java.util.Map.of()); + } +} +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +{{% notes type="warning" %}} + +Unfortunately, YAML lacks the language facilities to author components. Feel free to [skip ahead](/docs/iac/get-started/kubernetes/destroy-stack/). + +{{% /notes %}} + +{{% /choosable %}} + +This defines a component but it doesn't do much yet. + +### Refactor your code into the component + +Next, make three changes: + +1. Move all resources from {{< langfile >}} into the component's constructor +2. Change each resource to use the component [as the `parent`](/docs/iac/concepts/options/parent/) +3. Assign the service output to the `ip` property of the component + +The resulting {{< compfile >}} file will look like this; you can make each edit one at a time if preferred +to get a feel for things, or simply paste the contents of this into {{< compfile >}}: + +{{% choosable language typescript %}} + +```typescript +import * as pulumi from "@pulumi/pulumi"; +import * as k8s from "@pulumi/kubernetes"; + +// Arguments for the Kubernetes NGINX service component. +export interface KubernetesNginxServiceArgs { + isMinikube: boolean; // whether running on minikube. +} + +// A component that encapsulates creating a Kubernetes NGINX deployment and service. +export class KubernetesNginxService extends pulumi.ComponentResource { + public readonly ip: pulumi.Output; // the service ip. + + constructor(name: string, args: KubernetesNginxServiceArgs, opts?: pulumi.ComponentResourceOptions) { + super("quickstart:index:KubernetesNginxService", name, args, opts); + + const appName = "nginx"; + const appLabels = { app: appName }; + const deployment = new k8s.apps.v1.Deployment(appName, { + spec: { + selector: { matchLabels: appLabels }, + replicas: 1, + template: { + metadata: { labels: appLabels }, + spec: { containers: [{ name: appName, image: "nginx" }] } + } + } + }, { + parent: this, + }); + + // Allocate an IP to the Deployment. + const frontend = new k8s.core.v1.Service(appName, { + metadata: { labels: deployment.spec.template.metadata.labels }, + spec: { + type: args.isMinikube ? "ClusterIP" : "LoadBalancer", + ports: [{ port: 80, targetPort: 80, protocol: "TCP" }], + selector: appLabels + } + }, { + parent: this, + }); + + // Capture the IP and make it available as a component property and output: + this.ip = args.isMinikube + ? frontend.spec.clusterIP + : frontend.status.loadBalancer.apply( + (lb) => lb.ingress[0].ip || lb.ingress[0].hostname + ); + this.registerOutputs({ ip: this.ip }); // Signal component completion. + } +} +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +import pulumi +from pulumi_kubernetes.apps.v1 import Deployment +from pulumi_kubernetes.core.v1 import Service + +# A component that encapsulates creating a Kubernetes NGINX deployment and service. +class KubernetesNginxService(pulumi.ComponentResource): + def __init__(self, name: str, is_minikube: bool = False, opts = None): + super().__init__('quickstart:index:KubernetesNginxService', name, { 'isMinikube': is_minikube }, opts) + + app_name = "nginx" + app_labels = { "app": app_name } + + deployment = Deployment( + app_name, + spec={ + "selector": { "match_labels": app_labels }, + "replicas": 1, + "template": { + "metadata": { "labels": app_labels }, + "spec": { "containers": [{ "name": app_name, "image": "nginx" }] } + } + }, + opts=pulumi.ResourceOptions(parent=self)) + + # Allocate an IP to the Deployment. + frontend = Service( + app_name, + metadata={ + "labels": deployment.spec["template"]["metadata"]["labels"], + }, + spec={ + "type": "ClusterIP" if is_minikube else "LoadBalancer", + "ports": [{ "port": 80, "target_port": 80, "protocol": "TCP" }], + "selector": app_labels, + }, + opts=pulumi.ResourceOptions(parent=self)) + + # Capture the IP and make it available as a component property and output + result = None + if is_minikube: + result = frontend.spec.apply(lambda v: v["cluster_ip"] if "cluster_ip" in v else None) + else: + ingress = frontend.status.load_balancer.apply(lambda v: v["ingress"][0] if "ingress" in v else "output") + result = ingress.apply(lambda v: v["ip"] if v and "ip" in v else (v["hostname"] if v and "hostname" in v else "output")) + + self.ip = result + self.register_outputs({ 'ip': self.ip }) # Signal component completion. +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +package main + +import ( + appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1" + corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1" + metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type KubernetesNginxService struct { + pulumi.ResourceState + Ip pulumi.StringOutput // the service ip. +} + +type KubernetesNginxServiceArgs struct { + IsMinikube bool // whether running on minikube. +} + +func NewKubernetesNginxService(ctx *pulumi.Context, name string, args KubernetesNginxServiceArgs, opts ...pulumi.ResourceOption) (*KubernetesNginxService, error) { + self := &KubernetesNginxService{} + err := ctx.RegisterComponentResource("quickstart:index:KubernetesNginxService", name, self, opts...) + if err != nil { + return nil, err + } + + appName := "nginx" + appLabels := pulumi.StringMap{ + "app": pulumi.String(appName), + } + deployment, err := appsv1.NewDeployment(ctx, appName, &appsv1.DeploymentArgs{ + Spec: appsv1.DeploymentSpecArgs{ + Selector: &metav1.LabelSelectorArgs{ + MatchLabels: appLabels, + }, + Replicas: pulumi.Int(1), + Template: &corev1.PodTemplateSpecArgs{ + Metadata: &metav1.ObjectMetaArgs{ + Labels: appLabels, + }, + Spec: &corev1.PodSpecArgs{ + Containers: corev1.ContainerArray{ + corev1.ContainerArgs{ + Name: pulumi.String("nginx"), + Image: pulumi.String("nginx"), + }}, + }, + }, + }, + }, pulumi.Parent(self)) + if err != nil { + return nil, err + } + + feType := "LoadBalancer" + if args.IsMinikube { + feType = "ClusterIP" + } + + template := deployment.Spec.ApplyT(func(v appsv1.DeploymentSpec) *corev1.PodTemplateSpec { + return &v.Template + }).(corev1.PodTemplateSpecPtrOutput) + + meta := template.ApplyT(func(v *corev1.PodTemplateSpec) *metav1.ObjectMeta { return v.Metadata }).(metav1.ObjectMetaPtrOutput) + + frontend, _ := corev1.NewService(ctx, appName, &corev1.ServiceArgs{ + Metadata: meta, + Spec: &corev1.ServiceSpecArgs{ + Type: pulumi.String(feType), + Ports: &corev1.ServicePortArray{ + &corev1.ServicePortArgs{ + Port: pulumi.Int(80), + TargetPort: pulumi.Int(80), + Protocol: pulumi.String("TCP"), + }, + }, + Selector: appLabels, + }, + }, pulumi.Parent(self)) + + var ip pulumi.StringOutput + + if args.IsMinikube { + ip = frontend.Spec.ApplyT(func(val corev1.ServiceSpec) string { + if val.ClusterIP != nil { + return *val.ClusterIP + } + return "" + }).(pulumi.StringOutput) + } else { + ip = frontend.Status.ApplyT(func(val *corev1.ServiceStatus) string { + if val.LoadBalancer.Ingress != nil && len(val.LoadBalancer.Ingress) > 0 { + ingress := val.LoadBalancer.Ingress[0] + if ingress.Ip != nil { + return *ingress.Ip + } + if ingress.Hostname != nil { + return *ingress.Hostname + } + } + return "" + }).(pulumi.StringOutput) + } + + self.Ip = ip + ctx.RegisterResourceOutputs(self, pulumi.Map{"ip": ip}) // Signal component completion. + return self, nil +} +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +using Pulumi; +using Pulumi.Kubernetes.Core.V1; +using Pulumi.Kubernetes.Types.Inputs.Core.V1; +using Pulumi.Kubernetes.Types.Inputs.Apps.V1; +using Pulumi.Kubernetes.Types.Inputs.Meta.V1; +using System.Collections.Generic; + +public class KubernetesNginxServiceArgs +{ + public bool IsMinikube { get; set; } +} + +public class KubernetesNginxService : Pulumi.ComponentResource +{ + public Output Ip { get; private set; } + + public KubernetesNginxService(string name, KubernetesNginxServiceArgs args, ComponentResourceOptions? opts = null) + : base("quickstart:index:KubernetesNginxService", name, opts) + { + var appName = "nginx"; + var appLabels = new InputMap + { + { "app", appName }, + }; + + var deployment = new Pulumi.Kubernetes.Apps.V1.Deployment(appName, new DeploymentArgs + { + Spec = new DeploymentSpecArgs + { + Selector = new LabelSelectorArgs + { + MatchLabels = appLabels, + }, + Replicas = 1, + Template = new PodTemplateSpecArgs + { + Metadata = new ObjectMetaArgs + { + Labels = appLabels, + }, + Spec = new PodSpecArgs + { + Containers = + { + new ContainerArgs + { + Name = appName, + Image = "nginx", + Ports = + { + new ContainerPortArgs + { + ContainerPortValue = 80 + }, + }, + }, + }, + }, + }, + }, + }, new CustomResourceOptions + { + Parent = this, + }); + + var frontend = new Service(appName, new ServiceArgs + { + Metadata = new ObjectMetaArgs + { + Labels = deployment.Spec.Apply(spec => + spec.Template.Metadata.Labels + ), + }, + Spec = new ServiceSpecArgs + { + Type = args.IsMinikube + ? "ClusterIP" + : "LoadBalancer", + Selector = appLabels, + Ports = new ServicePortArgs + { + Port = 80, + TargetPort = 80, + Protocol = "TCP", + }, + } + }, new CustomResourceOptions + { + Parent = this, + }); + + this.Ip = args.IsMinikube + ? frontend.Spec.Apply(spec => spec.ClusterIP) + : frontend.Status.Apply(status => + { + var ingress = status.LoadBalancer.Ingress[0]; + return ingress.Ip ?? ingress.Hostname; + }); + + this.RegisterOutputs(new Dictionary{ + ["ip"] = this.Ip + }); + } +} +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +package myproject; + +import com.pulumi.*; +import com.pulumi.core.*; +import com.pulumi.resources.*; + +import com.pulumi.kubernetes.apps.v1.Deployment; +import com.pulumi.kubernetes.apps.v1.DeploymentArgs; +import com.pulumi.kubernetes.apps.v1.inputs.DeploymentSpecArgs; +import com.pulumi.kubernetes.core.v1.*; +import com.pulumi.kubernetes.core.v1.ServiceArgs; +import com.pulumi.kubernetes.core.v1.enums.ServiceSpecType; +import com.pulumi.kubernetes.core.v1.inputs.*; +import com.pulumi.kubernetes.meta.v1.inputs.LabelSelectorArgs; +import com.pulumi.kubernetes.meta.v1.inputs.ObjectMetaArgs; +import java.util.Map; + +class KubernetesNginxServiceArgs extends ResourceArgs { + public boolean isMinikube; + public KubernetesNginxServiceArgs(boolean isMinikube) { + this.isMinikube = isMinikube; + } +} + +class KubernetesNginxService extends ComponentResource { + public Output ip; + + public KubernetesNginxService(String name, KubernetesNginxServiceArgs args) { + this(name, args, null); + } + + public KubernetesNginxService(String name, KubernetesNginxServiceArgs args, ComponentResourceOptions opts) { + super("quickstart:index:KubernetesNginxService", name, args, opts); + + var labels = Map.of("app", "nginx"); + + var deployment = new Deployment("nginx", DeploymentArgs.builder() + .spec(DeploymentSpecArgs.builder() + .selector(LabelSelectorArgs.builder() + .matchLabels(labels) + .build()) + .replicas(1) + .template(PodTemplateSpecArgs.builder() + .metadata(ObjectMetaArgs.builder() + .labels(labels) + .build()) + .spec(PodSpecArgs.builder() + .containers(ContainerArgs.builder() + .name("nginx") + .image("nginx") + .ports(ContainerPortArgs.builder() + .containerPort(80) + .build()) + .build()) + .build()) + .build()) + .build()) + .build(), CustomResourceOptions.builder().parent(this).build()); + + var frontend = new Service("nginx", ServiceArgs.builder() + .metadata(ObjectMetaArgs.builder() + .labels(labels) + .build()) + .spec(ServiceSpecArgs.builder() + .type(args.isMinikube ? ServiceSpecType.ClusterIP : ServiceSpecType.LoadBalancer) + .selector(labels) + .ports(ServicePortArgs.builder() + .port(80) + .targetPort(80) + .protocol("TCP") + .build()) + .build()) + .build(), CustomResourceOptions.builder().parent(this).build()); + + // Export the service cluster IP (available for both ClusterIP and LoadBalancer types) + this.ip = frontend.spec().applyValue(spec -> spec.clusterIP().orElse("pending")); + this.registerOutputs(Map.of("ip", this.ip)); + } +} +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +{{% notes type="warning" %}} + +Unfortunately, YAML lacks the language facilities to author components. Feel free to [skip ahead](/docs/iac/get-started/kubernetes/destroy-stack/). + +{{% /notes %}} + +{{% /choosable %}} + +### Instantiate the component + +Now go back to your original file {{< langfile >}}. Now that you have moved all of the resources, you can start over with a clean slate. +Ensure the file is empty and we will build it back up by simply importing and instantiating our new component. + +Add this to your now-empty {{< langfile >}}: + +{{% choosable language typescript %}} + +```typescript +import * as pulumi from "@pulumi/pulumi"; + +// Import from our new component module: +import { KubernetesNginxService } from "./nginx"; + +// Read the configuration value: +const config = new pulumi.Config(); + +// Create an instance of our component: +const nginx = new KubernetesNginxService("my-nginx", { + isMinikube: config.requireBoolean("isMinikube") +}); + +// And export its autoassigned IP: +export const ip = nginx.ip; +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +import pulumi + +# Import from our new component module: +from nginx import KubernetesNginxService + +# Read the configuration value: +config = pulumi.Config() + +# Create an instance of our component: +nginx = KubernetesNginxService('my-nginx', is_minikube=config.require_bool("isMinikube")) + +# And export its autoassigned IP: +pulumi.export("ip", nginx.ip) +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +package main + +import ( + "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 { + // Read the configuration value: + isMinikube := config.GetBool(ctx, "isMinikube") + + // Create an instance of our component: + nginx, err := NewKubernetesNginxService(ctx, "my-nginx", KubernetesNginxServiceArgs{ + IsMinikube: isMinikube, + }) + if err != nil { + return err + } + + // And export its autoassigned IP: + ctx.Export("ip", nginx.Ip) + return nil + }) +} + +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +using Pulumi; +using System.Collections.Generic; + +return await Pulumi.Deployment.RunAsync(() => +{ + // Read the configuration value: + var config = new Pulumi.Config(); + + // Create an instance of our component: + var nginx = new KubernetesNginxService("my-nginx", new KubernetesNginxServiceArgs() + { + IsMinikube = config.GetBoolean("isMinikube") ?? false + }); + + // And export its autoassigned IP: + return new Dictionary + { + ["ip"] = nginx.Ip + }; +}); +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +package myproject; + +import com.pulumi.Pulumi; + +public class App { + public static void main(String[] args) { + Pulumi.run(ctx -> { + // Read the configuration value: + var config = ctx.config(); + + // Create an instance of our component: + var nginx = new KubernetesNginxService("my-nginx", + new KubernetesNginxServiceArgs(config.requireBoolean("isMinikube"))); + + // And export its autoassigned IP: + ctx.export("ip", nginx.ip); + }); + } +} +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +{{% notes type="warning" %}} + +Unfortunately, YAML lacks the language facilities to author components. Feel free to [skip ahead](/docs/iac/get-started/kubernetes/destroy-stack/). + +{{% /notes %}} + +{{% /choosable %}} + +### Deploy the component + +Now deploy the resulting component instantiation. To do so, run `pulumi up` as usual: + +``` +$ pulumi up +Previewing update (dev) + + Type Name Plan + pulumi:pulumi:Stack quickstart-dev + + ├─ quickstart:index:KubernetesNginxService my-nginx create + + │ ├─ kubernetes:apps/v1:Deployment nginx create + + │ └─ kubernetes:core/v1:Service nginx create + - ├─ kubernetes:core/v1:Service nginx delete + - └─ kubernetes:apps/v1:Deployment nginx delete + +Outputs: + ~ ip: "10.110.183.208" => "10.96.0.0" + +Resources: + + 3 to create + - 2 to delete + 5 changes. 1 unchanged + +Do you want to perform this update? [Use arrows to move, type to filter] + yes +> no + details +``` + +This preview shows you a few things. First, you'll see our `KubernetesNginxService` component with all of its children resources neatly parented underneath it. This helps to see what resources relate to which components. Next, you'll see that your old resources are being destroyed. + +{{% notes type="info" %}} + +If you're wondering why Pulumi didn't simply update the resources in place, it's because certain changes -- like +refactoring resources into a component -- fundamentally change a resource's identity. Many changes like updating +properties or moving resources between files are not disruptive like this. In such cases, you can assign +[aliases](/docs/iac/concepts/options/aliases/) to prevent deletions from happening. + +{{% /notes %}} + +Accept the changes by selecting `yes` and the deployment will occur: + +``` +Updating (dev) + + Type Name Status + pulumi:pulumi:Stack quickstart-dev + + ├─ quickstart:index:KubernetesNginxService my-nginx created (2s) + + │ ├─ kubernetes:apps/v1:Deployment nginx created (1s) + + │ └─ kubernetes:core/v1:Service nginx created (9s) + - ├─ kubernetes:core/v1:Service nginx deleted (4s) + - └─ kubernetes:apps/v1:Deployment nginx deleted (0.92s) + +Outputs: + ~ ip: "10.110.183.208" => "10.103.199.118" + +Resources: + + 3 created + - 2 deleted + 5 changes. 1 unchanged + +Duration: 13s +``` + +Now test out your new NGINX service -- it works like before, just with a tidier codebase now! + +{{% notes type="info" %}} +**If using Minikube:** Minikube does not support type `LoadBalancer`. Instead, forward the NGINX service: + +```bash +$ kubectl get service +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +kubernetes ClusterIP 10.96.0.1 443/TCP 44h +nginx-9e5d5cd4 ClusterIP 10.103.199.118 80/TCP 6m47s +``` + +The assigned name for this particular nginx service is `nginx-9e5d5cd4`; yours will be different. In a new terminal window, run: + +```bash +$ kubectl port-forward service/nginx-9e5d5cd4 8080:80 +Forwarding from 127.0.0.1:8080 -> 80 +Forwarding from [::1]:8080 -> 80 +``` + +{{% /notes %}} + +You can curl NGINX to verify it is running: + +{{% choosable os "macos,linux" %}} + +```bash +$ $(pulumi config get isMinikube) && curl "http://localhost:8080" || curl $(pulumi stack output ip) +``` + +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> if (pulumi config get isMinikube) { curl "http://localhost:8080" } else { curl $(pulumi stack output ip) } +``` + +{{% /choosable %}} + +Expected output: + +```html + + + +Welcome to nginx! + + + +

Welcome to nginx!

+

If you see this page, the nginx web server is successfully installed and +working. Further configuration is required.

+ +

For online documentation and support please refer to +nginx.org.
+Commercial support is available at +nginx.com.

+ +

Thank you for using nginx.

+ + +``` + +Once you are ready to move on, destroy everything you've provisioned in this tutorial. + +{{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/kubernetes/create-project.md b/content/docs/iac/get-started/kubernetes/create-project.md index a10a8f541c45..33514cceb650 100644 --- a/content/docs/iac/get-started/kubernetes/create-project.md +++ b/content/docs/iac/get-started/kubernetes/create-project.md @@ -3,99 +3,173 @@ title_tag: Create a New Project | Kubernetes meta_desc: This page provides an overview of how to create a new Kubernetes + Pulumi project. title: Create project h1: "Pulumi & Kubernetes: Create project" -weight: 3 +weight: 4 menu: iac: name: Create project identifier: kubernetes-get-started.create-project parent: kubernetes-get-started - weight: 3 + weight: 4 aliases: - /docs/quickstart/kubernetes/create-project/ + - /docs/quickstart/kubernetes/review-project/ --- -Now that you have set up your environment let's create your first Pulumi program. -{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} +## Create a new project + +A [**project**](/docs/iac/concepts/projects) is a program in your chosen language that defines a collection of related cloud resources. In this step, you will create a new project. + +### Initializing your project + +Each project lives in its own directory. Create a new one: + +{{% choosable os "linux,macos" %}} + +```bash +$ mkdir quickstart +``` + +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> mkdir quickstart +``` + +{{% /choosable %}} + +Change into the new directory: + +{{% choosable os "linux,macos" %}} + +```bash +$ cd quickstart +``` + +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> cd quickstart +``` + +{{% /choosable %}} + +Now initialize a new Pulumi project for Kubernetes using the `pulumi new` command: {{% choosable language typescript %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new kubernetes-typescript ``` +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi new kubernetes-typescript +``` + +{{% /choosable %}} + {{% /choosable %}} {{% choosable language python %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new kubernetes-python ``` +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi new kubernetes-python +``` + +{{% /choosable %}} + {{% /choosable %}} {{% choosable language go %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new kubernetes-go ``` +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi new kubernetes-go +``` + +{{% /choosable %}} + {{% /choosable %}} {{% choosable language csharp %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new kubernetes-csharp ``` +{{% /choosable %}} +{{% choosable os "windows" %}} + +```powershell +> pulumi new kubernetes-csharp +``` + {{% /choosable %}} +{{% /choosable %}} {{% choosable language java %}} +{{% choosable os "linux,macos" %}} + ```bash -$ mkdir quickstart && cd quickstart $ pulumi new kubernetes-java ``` {{% /choosable %}} +{{% choosable os "windows" %}} -{{% choosable language yaml %}} - -```bash -$ mkdir quickstart && cd quickstart -$ pulumi new kubernetes-yaml +```powershell +> pulumi new kubernetes-java ``` {{% /choosable %}} -{{< cli-note >}} - -After logging in, the CLI will proceed with walking you through creating a new project. +{{% /choosable %}} +{{% choosable language yaml %}} -First, you will be asked for a **project name** and **project description**. Hit `ENTER` to accept the default values or specify new values. +{{% choosable os "linux,macos" %}} +```bash +$ pulumi new kubernetes-yaml ``` -This command will walk you through creating a new Pulumi project. -Enter a value or leave blank to accept the (default), and press . -Press ^C at any time to quit. +{{% /choosable %}} +{{% choosable os "windows" %}} -project name: (quickstart) -project description: (A minimal Kubernetes Pulumi program) -Created project 'quickstart' +```powershell +> pulumi new kubernetes-yaml ``` -Next, you will be asked for a **stack name**. You can hit `ENTER` to accept the default value of `dev`. +{{% /choosable %}} -``` -Please enter your desired stack name. -To create a stack in an organization, use the format / (e.g. `acmecorp/dev`). -stack name: (dev) -Created stack 'dev' -``` +{{% /choosable %}} -> What are [projects](/docs/concepts/projects/) and [stacks](/docs/concepts/stack/)? Pulumi projects and stacks let you organize Pulumi code. Consider a Pulumi _project_ to be analogous to a GitHub repo---a single place for code---and a _stack_ to be an instance of that code with a separate configuration. For instance, _Project Foo_ may have multiple stacks for different development environments (Dev, Test, or Prod), or perhaps for different cloud configurations (geographic region for example). See [Organizing Projects and Stacks](/docs/using-pulumi/organizing-projects-stacks/) for some best practices on organizing your Pulumi projects and stacks. +The `pulumi new` command interactively walks through initializing a new project, as well as creating a [**stack**](/docs/iac/concepts/stacks) and [**configuring**](/docs/iac/concepts/config) it. A stack is an instance of your project and you may have many of them -- like `dev`, `staging`, and `prod` -- each with different configuration settings. + +{{< cli-note >}} {{% choosable language "typescript" %}} @@ -133,6 +207,299 @@ After the command completes, the project and stack will be ready. {{% /choosable %}} -Next, we'll review the generated project files. +### Review your new project's contents + +Review some of the generated project files: + +{{% choosable language "typescript,python,go,csharp,java" %}} + +- `Pulumi.yaml` defines the [project](/docs/concepts/projects/). + +{{% /choosable %}} + +{{% choosable language yaml %}} + +- `Pulumi.yaml` defines both the [project](/docs/concepts/projects/) and the program that manages your stack resources. + +{{% /choosable %}} + +- If present, `Pulumi.dev.yaml` would contain [configuration](/docs/concepts/config/) values for the [stack](/docs/concepts/stack/) we initialized. However, it's empty since this project doesn't require any configuration. + +{{% choosable language java %}} + +- `src/main/java/myproject` defines the project's Java package root. + +{{% /choosable %}} + +{{% choosable language python %}} + +- `__main__.py` is the Pulumi program that defines your stack resources. + +{{% /choosable %}} + +{{% choosable language "typescript,go,csharp,java" %}} + +- {{< langfile >}} is the Pulumi program that defines your stack resources. + +{{% /choosable %}} + +Examine {{< langfile >}}: + +{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} + +{{% choosable language typescript %}} + +```typescript +import * as k8s from "@pulumi/kubernetes"; + +const appLabels = { app: "nginx" }; +const deployment = new k8s.apps.v1.Deployment("nginx", { + spec: { + selector: { matchLabels: appLabels }, + replicas: 1, + template: { + metadata: { labels: appLabels }, + spec: { containers: [{ name: "nginx", image: "nginx" }] } + } + } +}); +export const name = deployment.metadata.name; +``` + +{{% /choosable %}} +{{% choosable language python %}} + +```python +"""A Kubernetes Python Pulumi program""" + +import pulumi +from pulumi_kubernetes.apps.v1 import Deployment, DeploymentSpecArgs +from pulumi_kubernetes.meta.v1 import LabelSelectorArgs, ObjectMetaArgs +from pulumi_kubernetes.core.v1 import ContainerArgs, PodSpecArgs, PodTemplateSpecArgs + +app_labels = { "app": "nginx" } + +deployment = Deployment( + "nginx", + spec={ + "selector": { "match_labels": app_labels }, + "replicas": 1, + "template": { + "metadata": { "labels": app_labels }, + "spec": { "containers": [{ "name": "nginx", "image": "nginx" }] } + }, + }) + +pulumi.export("name", deployment.metadata["name"]) +``` + +{{% /choosable %}} +{{% choosable language go %}} + +```go +package main + +import ( + appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1" + corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1" + metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + + appLabels := pulumi.StringMap{ + "app": pulumi.String("nginx"), + } + deployment, err := appsv1.NewDeployment(ctx, "app-dep", &appsv1.DeploymentArgs{ + Spec: appsv1.DeploymentSpecArgs{ + Selector: &metav1.LabelSelectorArgs{ + MatchLabels: appLabels, + }, + Replicas: pulumi.Int(1), + Template: &corev1.PodTemplateSpecArgs{ + Metadata: &metav1.ObjectMetaArgs{ + Labels: appLabels, + }, + Spec: &corev1.PodSpecArgs{ + Containers: corev1.ContainerArray{ + corev1.ContainerArgs{ + Name: pulumi.String("nginx"), + Image: pulumi.String("nginx"), + }}, + }, + }, + }, + }) + if err != nil { + return err + } + + ctx.Export("name", deployment.Metadata.Name()) + + return nil + }) +} + +``` + +{{% /choosable %}} +{{% choosable language csharp %}} + +```csharp +using Pulumi; +using Pulumi.Kubernetes.Types.Inputs.Core.V1; +using Pulumi.Kubernetes.Types.Inputs.Apps.V1; +using Pulumi.Kubernetes.Types.Inputs.Meta.V1; +using System.Collections.Generic; + +return await Deployment.RunAsync(() => +{ + var appLabels = new InputMap + { + { "app", "nginx" } + }; + + var deployment = new Pulumi.Kubernetes.Apps.V1.Deployment("nginx", new DeploymentArgs + { + Spec = new DeploymentSpecArgs + { + Selector = new LabelSelectorArgs + { + MatchLabels = appLabels + }, + Replicas = 1, + Template = new PodTemplateSpecArgs + { + Metadata = new ObjectMetaArgs + { + Labels = appLabels + }, + Spec = new PodSpecArgs + { + Containers = + { + new ContainerArgs + { + Name = "nginx", + Image = "nginx", + Ports = + { + new ContainerPortArgs + { + ContainerPortValue = 80 + } + } + } + } + } + } + } + }); + + // export the deployment name + return new Dictionary + { + ["name"] = deployment.Metadata.Apply(m => m.Name) + }; +}); +``` + +{{% /choosable %}} + +{{% choosable language java %}} + +```java +package myproject; + +import com.pulumi.Pulumi; +import com.pulumi.kubernetes.apps.v1.Deployment; +import com.pulumi.kubernetes.apps.v1.DeploymentArgs; +import com.pulumi.kubernetes.apps.v1.inputs.DeploymentSpecArgs; +import com.pulumi.kubernetes.core.v1.inputs.ContainerArgs; +import com.pulumi.kubernetes.core.v1.inputs.ContainerPortArgs; +import com.pulumi.kubernetes.core.v1.inputs.PodSpecArgs; +import com.pulumi.kubernetes.core.v1.inputs.PodTemplateSpecArgs; +import com.pulumi.kubernetes.meta.v1.inputs.LabelSelectorArgs; +import com.pulumi.kubernetes.meta.v1.inputs.ObjectMetaArgs; + +import java.util.Map; + +public class App { + public static void main(String[] args) { + Pulumi.run(ctx -> { + var labels = Map.of("app", "nginx"); + + var deployment = new Deployment("nginx", DeploymentArgs.builder() + .spec(DeploymentSpecArgs.builder() + .selector(LabelSelectorArgs.builder() + .matchLabels(labels) + .build()) + .replicas(1) + .template(PodTemplateSpecArgs.builder() + .metadata(ObjectMetaArgs.builder() + .labels(labels) + .build()) + .spec(PodSpecArgs.builder() + .containers(ContainerArgs.builder() + .name("nginx") + .image("nginx") + .ports(ContainerPortArgs.builder() + .containerPort(80) + .build()) + .build()) + .build()) + .build()) + + .build()) + .build()); + + var name = deployment.metadata() + .applyValue(m -> m.orElseThrow().name().orElse("")); + + ctx.export("name", name); + }); + } +} +``` + +{{% /choosable %}} + +{{% choosable language yaml %}} + +```yaml +name: quickstart +runtime: yaml +description: A minimal Kubernetes Pulumi YAML program + +variables: + appLabels: + app: nginx + +resources: + deployment: + name: nginx + type: kubernetes:apps/v1:Deployment + properties: + spec: + selector: + matchLabels: ${appLabels} + replicas: 1 + template: + metadata: + labels: ${appLabels} + spec: + containers: + - name: nginx + image: nginx + +outputs: + name: ${deployment.metadata.name} +``` + +{{% /choosable %}} + +This Pulumi program creates an NGINX deployment and exports the name of the deployment. {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/kubernetes/deploy-changes.md b/content/docs/iac/get-started/kubernetes/deploy-changes.md deleted file mode 100644 index 73e191004351..000000000000 --- a/content/docs/iac/get-started/kubernetes/deploy-changes.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -title_tag: Deploy the Changes | Kubernetes -meta_desc: This page provides an overview of how deploy changes to a Kubernetes project. -title: Deploy changes -h1: "Pulumi & Kubernetes: Deploy changes" -weight: 7 -menu: - iac: - name: Deploy changes - identifier: kubernetes-get-started.deploy-changes - parent: kubernetes-get-started - weight: 7 - -aliases: - - /docs/quickstart/kubernetes/deploy-changes/ ---- - -Deploy the stack changes. - -```bash -$ pulumi up -``` - -Pulumi computes the minimally disruptive change to achieve the desired state described by the program. - -``` -Previewing update (dev): - Type Name Plan - pulumi:pulumi:Stack quickstart-dev - + └─ kubernetes:core/v1:Service nginx create - -Outputs: - + ip : "10.96.0.0" - - name: "nginx-bec13562" - -Resources: - + 1 to create - 2 unchanged - -Do you want to perform this update? [Use arrows to move, type to filter] -> yes - no - details -``` - -Select `yes` using the arrows and hit enter to update the resources in Kubernetes. - -Pulumi will create the service since it is now defined in the program. - -``` -Do you want to perform this update? yes -Updating (dev): - Type Name Status - pulumi:pulumi:Stack quickstart-dev - + └─ kubernetes:core/v1:Service nginx created (10s) - -Outputs: - + ip : "10.110.183.208" - - name: "nginx-bec13562" - -Resources: - + 1 created - 2 unchanged - -Duration: 12s -``` - -View the `ip` [stack output](/docs/concepts/stack#outputs) from the nginx service. - -```bash -$ pulumi stack output ip -``` - -> **If using Minikube:** Minikube does not support type `LoadBalancer`. Instead, forward the nginx service: -> -> ```bash -> $ kubectl get service -> NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -> kubernetes ClusterIP 10.96.0.1 443/TCP 44h -> nginx-9e5d5cd4 ClusterIP 10.103.199.118 80/TCP 6m47s -> ``` -> -> Note: The assigned name for this particular nginx service is `nginx-9e5d5cd4`; yours will be different. In a new terminal window, run: -> -> ```bash -> $ kubectl port-forward service/nginx-9e5d5cd4 8080:80 -> Forwarding from 127.0.0.1:8080 -> 80 -> Forwarding from [::1]:8080 -> 80 -> ``` - -Curl nginx to verify it is running. - -```bash -$ $(pulumi config get isMinikube) && curl "http://localhost:8080" || curl $(pulumi stack output ip) - - - -Welcome to nginx! - - - -

Welcome to nginx!

-

If you see this page, the nginx web server is successfully installed and -working. Further configuration is required.

- -

For online documentation and support please refer to -nginx.org.
-Commercial support is available at -nginx.com.

- -

Thank you for using nginx.

- - -``` - -Next, we'll destroy the stack. - -{{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/kubernetes/deploy-stack.md b/content/docs/iac/get-started/kubernetes/deploy-stack.md index 289e273bb99f..4ece3588e0d1 100644 --- a/content/docs/iac/get-started/kubernetes/deploy-stack.md +++ b/content/docs/iac/get-started/kubernetes/deploy-stack.md @@ -1,12 +1,12 @@ --- title_tag: Deploy the Stack | Kubernetes -meta_desc: This page provides an overview of how to deploy a Kubernetes project as a Pulumi Stack. -title: Deploy stack -h1: "Pulumi & Kubernetes: Deploy stack" +meta_desc: Learn how to deploy your stack to a Kubernetes project in this guide. +title: Deploy to Kubernetes +h1: "Get started with Pulumi and Kubernetes" weight: 5 menu: iac: - name: Deploy stack + name: Deploy identifier: kubernetes-get-started.deploy-stack parent: kubernetes-get-started weight: 5 @@ -15,38 +15,53 @@ aliases: - /docs/quickstart/kubernetes/deploy-stack/ --- -Deploy the stack: +## Deploy to Kubernetes + +Now run `pulumi up` to start deploying your NGINX deployment: + +{{% choosable "os" "macos,linux" %}} ```bash $ pulumi up ``` -This command instructs Pulumi to determine the resources needed to create the stack. A preview is shown of the changes that will be made: +{{% /choosable %}} +{{% choosable "os" "windows" %}} -```bash -Previewing update (dev) +```powershell +> pulumi up +``` + +{{% /choosable %}} + +This command first shows you a **preview** of the changes that will be made: + +``` +Previewing update (dev): Type Name Plan + pulumi:pulumi:Stack quickstart-dev create + └─ kubernetes:apps/v1:Deployment nginx create -Outputs: - name: "nginx-516e16fd" - Resources: + 2 to create -Do you want to perform this update? [Use arrows to move, type to filter] +Do you want to perform this update? > yes no details ``` -Select `yes` using the arrows and hit enter to create the resources in Kubernetes. +No changes have been made yet. You may decline to proceed by selecting `no` or choose `details` to see more information about the proposed update like your deployment's properties. -```bash +### Performing the update + +To proceed and deploy your NGINX deployment, select `yes`. This begins an **update**: + +``` Do you want to perform this update? yes Updating (dev): + Type Name Status + pulumi:pulumi:Stack quickstart-dev created (3s) + └─ kubernetes:apps/v1:Deployment nginx created (2s) @@ -60,16 +75,30 @@ Resources: Duration: 4s ``` -The `name` of the deployment that we exported is shown as a [stack output](/docs/concepts/stack#outputs). +Updates can take some time since they wait for the Kubernetes resources to finish being created. The deployment will finish in just a few seconds. {{< auto-naming-note resource="deployment" suffix="bec13562" >}} -{{< console-note >}} +{{% notes type="warning" %}} +If you get the error `configured Kubernetes cluster is unreachable` or +`unable to load schema information from the API server`, verify your cluster access: -{{% notes type="info" %}} -If you get the error `configured Kubernetes cluster is unreachable: unable to load schema information from the API server: the server has asked for the client to provide credentials`, you may need to configure valid AWS credentials. +1. Check your kubeconfig: `kubectl config view` +2. Test cluster connectivity: `kubectl cluster-info` +3. Verify authentication: `kubectl auth can-i get pods` + +If these commands fail, return to the [Configure access](/docs/iac/get-started/kubernetes/configure/) +step to set up your Kubernetes cluster and kubectl. {{% /notes %}} -Next, we'll make some modifications to the program. +### View your update on Pulumi Cloud + +If you are logged into [Pulumi Cloud](/docs/pulumi-cloud), you'll see "View Live" hyperlinks in the CLI output during your update. These go to [a page](https://app.pulumi.com) with detailed information about your stack including resources, configuration, a full history of updates, and more. Navigate to it to review the details of your update: + + + A stack update with console output, as shown in the Pulumi Service + + +Now that the NGINX deployment has been provisioned, you'll update it to do something more interesting. {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/kubernetes/destroy-stack.md b/content/docs/iac/get-started/kubernetes/destroy-stack.md index c936bb638226..b331a6802e76 100644 --- a/content/docs/iac/get-started/kubernetes/destroy-stack.md +++ b/content/docs/iac/get-started/kubernetes/destroy-stack.md @@ -2,11 +2,11 @@ title_tag: Destroy the Stack | Kubernetes meta_desc: This page provides an overview of how to destroy a Pulumi stack of a Kubernetes project. title: Destroy stack -h1: "Pulumi & Kubernetes: Destroy stack" +h1: "Get started with Pulumi and Kubernetes" weight: 8 menu: iac: - name: Destroy stack + name: Cleanup & destroy identifier: kubernetes-get-started.destroy-stack parent: kubernetes-get-started weight: 8 @@ -15,28 +15,44 @@ aliases: - /docs/quickstart/kubernetes/destroy-stack/ --- -Now that we've seen how to deploy changes to our program, let's clean up and tear down the resources that are part of our stack. +## Cleanup & destroy the stack -To destroy resources, run the following: +Our final step is to clean up all of the resources we've allocated in this tutorial. + +Run the `pulumi destroy` command to delete all cloud resources in this project/stack: + +{{% choosable os "linux,macos" %}} ```bash $ pulumi destroy ``` -You'll be prompted to make sure you really want to delete these resources. +{{% /choosable %}} + +{{% choosable os "windows" %}} + +```powershell +> pulumi destroy +``` + +{{% /choosable %}} + +Just like `pulumi up`, you'll be shown a preview to ensure that you want to proceed: ```bash Previewing destroy (dev): - Type Name Plan - - pulumi:pulumi:Stack quickstart-dev delete - - ├─ kubernetes:core/v1:Service nginx delete - - └─ kubernetes:apps/v1:Deployment nginx delete + + Type Name Plan + - pulumi:pulumi:Stack quickstart-dev delete + - └─ quickstart:index:KubernetesNginxService my-nginx delete + - ├─ kubernetes:core/v1:Service nginx delete + - └─ kubernetes:apps/v1:Deployment nginx delete Outputs: - - ip: "10.110.183.208" + - ip: "172.183.217.156" Resources: - - 3 to delete + - 4 to delete Do you want to perform this destroy? [Use arrows to move, type to filter] > yes @@ -44,44 +60,50 @@ Do you want to perform this destroy? [Use arrows to move, type to filter] details ``` -Select `yes` using the arrows and hit enter to delete the resources in Kubernetes. +As with an update, we can choose `no` or `details`; select `yes` to proceed: -```bash +``` Do you want to perform this destroy? yes -Destroying (dev): - Type Name Status - - pulumi:pulumi:Stack quickstart-dev deleted (0.00s) - - ├─ kubernetes:core/v1:Service nginx deleted (0.94s) - - └─ kubernetes:apps/v1:Deployment nginx deleted (4s) +Destroying (dev) + + Type Name Status + - pulumi:pulumi:Stack quickstart-dev deleted (0.08s) + - └─ quickstart:index:KubernetesNginxService my-nginx deleted (0.08s) + - ├─ kubernetes:core/v1:Service nginx deleted (16s) + - └─ kubernetes:apps/v1:Deployment nginx deleted (0.59s) Outputs: - - ip: "10.110.183.208" + - ip: "172.183.217.156" Resources: - - 3 deleted + - 4 deleted -Duration: 6s - -The resources in the stack have been deleted, but the history and configuration associated with the stack are still maintained. -If you want to remove the stack completely, run `pulumi stack rm dev`. +Duration: 18s ``` -To delete the stack itself, run [`pulumi stack rm`](/docs/cli/commands/pulumi_stack_rm). Note that this removes the stack -entirely from Pulumi Cloud, along with all of its update history. +At this stage, your stack still exists, but all cloud resources have been deleted from it. + +## Remove the stack + +The final step is to remove the stack itself. Destroy keeps the stack around so that you still have the full +history of what happened to the stack. Running [`pulumi stack rm`](/docs/cli/commands/pulumi_stack_rm) will +delete it entirely, including all history and state snapshots. Be careful, this step cannot be undone! + +{{% choosable "os" "macos,linux" %}} ```bash $ pulumi stack rm -This will permanently remove the 'dev' stack! -Please confirm that this is what you`d like to do by typing `dev`: ``` -Type `dev` and hit enter to remove the stack. +{{% /choosable %}} +{{% choosable "os" "windows" %}} -```bash -Please confirm that this is what you`d like to do by typing `dev`: dev -Stack 'dev' has been removed! +```powershell +> pulumi stack rm ``` -Next, we'll look at some next steps. +{{% /choosable %}} + +You'll be prompted to confirm the removal. Confirm it to successfully complete this tutorial. {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/kubernetes/modify-program.md b/content/docs/iac/get-started/kubernetes/modify-program.md index 3e2b5a2955fd..fe4c54c80e9c 100644 --- a/content/docs/iac/get-started/kubernetes/modify-program.md +++ b/content/docs/iac/get-started/kubernetes/modify-program.md @@ -1,21 +1,22 @@ --- -title_tag: Modify the Program | Kubernetes -meta_desc: This page provides an overview on how to update Kubernetes project from a Pulumi program. -title: Modify program -h1: "Pulumi & Kubernetes: Modify program" +title_tag: Make an Update | Kubernetes +meta_desc: This page provides an overview on how to update a Kubernetes project from a Pulumi program. +title: Make an update +h1: "Get started with Pulumi and Kubernetes" weight: 6 menu: iac: - name: Modify program + name: Make an update identifier: kubernetes-get-started.modify-program parent: kubernetes-get-started weight: 6 aliases: - /docs/quickstart/kubernetes/modify-program/ + - /docs/quickstart/kubernetes/deploy-changes/ --- -Now that we have an instance of our Pulumi program deployed, let's update it to do something a little more interesting. +Now that you have an instance of your Pulumi program deployed, update it to do something a little more interesting. Replace the entire contents of {{< langfile >}} with the following: @@ -328,15 +329,15 @@ package myproject; import com.pulumi.Pulumi; import com.pulumi.core.Output; -import com.pulumi.kubernetes.apps_v1.Deployment; -import com.pulumi.kubernetes.apps_v1.DeploymentArgs; -import com.pulumi.kubernetes.apps_v1.inputs.DeploymentSpecArgs; -import com.pulumi.kubernetes.core_v1.*; -import com.pulumi.kubernetes.core_v1.ServiceArgs; -import com.pulumi.kubernetes.core_v1.enums.ServiceSpecType; -import com.pulumi.kubernetes.core_v1.inputs.*; -import com.pulumi.kubernetes.meta_v1.inputs.LabelSelectorArgs; -import com.pulumi.kubernetes.meta_v1.inputs.ObjectMetaArgs; +import com.pulumi.kubernetes.apps.v1.Deployment; +import com.pulumi.kubernetes.apps.v1.DeploymentArgs; +import com.pulumi.kubernetes.apps.v1.inputs.DeploymentSpecArgs; +import com.pulumi.kubernetes.core.v1.*; +import com.pulumi.kubernetes.core.v1.ServiceArgs; +import com.pulumi.kubernetes.core.v1.enums.ServiceSpecType; +import com.pulumi.kubernetes.core.v1.inputs.*; +import com.pulumi.kubernetes.meta.v1.inputs.LabelSelectorArgs; +import com.pulumi.kubernetes.meta.v1.inputs.ObjectMetaArgs; import java.util.Map; public class App { @@ -369,12 +370,9 @@ public class App { .build()) .build()); - var name = deployment.metadata() - .applyValue(m -> m.orElseThrow().name().orElse("")); - var frontend = new Service("nginx", ServiceArgs.builder() .metadata(ObjectMetaArgs.builder() - .labels(deployment.spec().applyValue(spec -> spec.get().template().metadata().get().labels())) + .labels(labels) .build()) .spec(ServiceSpecArgs.builder() .type(isMinikube ? ServiceSpecType.ClusterIP : ServiceSpecType.LoadBalancer) @@ -387,15 +385,8 @@ public class App { .build()) .build()); - ctx.export("ip", isMinikube - ? frontend.spec().applyValue(spec -> spec.get().clusterIP()) - : Output.tuple(frontend.status(), frontend.spec()).applyValue(t -> { - var status = t.t1; - var spec = t.t2; - var ingress = status.get().loadBalancer().get().ingress().get(0); - return ingress.ip().orElse(ingress.hostname().orElse(spec.get().clusterIP().get())); - }) - ); + // Export the service cluster IP (available for both ClusterIP and LoadBalancer types) + ctx.export("ip", frontend.spec().applyValue(spec -> spec.clusterIP().orElse("pending"))); }); } } @@ -458,6 +449,151 @@ If you are currently using Minikube, set `isMinikube` to `true`, otherwise, set $ pulumi config set isMinikube false ``` -Next, we'll deploy the changes. +### Deploy the changes + +To deploy the changes, run `pulumi up` again: + +{{% choosable "os" "macos,linux" %}} + +```bash +$ pulumi up +``` + +{{% /choosable %}} +{{% choosable "os" "windows" %}} + +```powershell +> pulumi up +``` + +{{% /choosable %}} + +Pulumi computes the minimally disruptive change to achieve the desired state described by the program. + +``` +Previewing update (dev): + Type Name Plan + pulumi:pulumi:Stack quickstart-dev + + └─ kubernetes:core/v1:Service nginx create + +Outputs: + + ip : "10.96.0.0" + - name: "nginx-bec13562" + +Resources: + + 1 to create + 2 unchanged + +Do you want to perform this update? +> yes + no + details +``` + +Select `yes` to proceed. Pulumi will create the new service resource: + +``` +Do you want to perform this update? yes +Updating (dev): + Type Name Status + pulumi:pulumi:Stack quickstart-dev + + └─ kubernetes:core/v1:Service nginx created (10s) + +Outputs: + + ip : "10.110.183.208" + - name: "nginx-bec13562" + +Resources: + + 1 created + 2 unchanged + +Duration: 12s +``` + +### Verify the deployment + +View the `ip` [stack output](/docs/concepts/stack#outputs) from the NGINX service: + +{{% choosable "os" "macos,linux" %}} + +```bash +$ pulumi stack output ip +``` + +{{% /choosable %}} +{{% choosable "os" "windows" %}} + +```powershell +> pulumi stack output ip +``` + +{{% /choosable %}} + +{{% notes type="info" %}} +**If using Minikube:** Minikube does not support type `LoadBalancer`. Instead, forward the NGINX service: + +```bash +$ kubectl get service +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +kubernetes ClusterIP 10.96.0.1 443/TCP 44h +nginx-9e5d5cd4 ClusterIP 10.103.199.118 80/TCP 6m47s +``` + +The assigned name for this particular nginx service is `nginx-9e5d5cd4`; yours will be different. In a new terminal window, run: + +```bash +$ kubectl port-forward service/nginx-9e5d5cd4 8080:80 +Forwarding from 127.0.0.1:8080 -> 80 +Forwarding from [::1]:8080 -> 80 +``` + +{{% /notes %}} + +You can curl NGINX to verify it is running: + +{{% choosable "os" "macos,linux" %}} + +```bash +$ $(pulumi config get isMinikube) && curl "http://localhost:8080" || curl $(pulumi stack output ip) +``` + +{{% /choosable %}} +{{% choosable "os" "windows" %}} + +```powershell +> if (pulumi config get isMinikube) { curl "http://localhost:8080" } else { curl $(pulumi stack output ip) } +``` + +{{% /choosable %}} + +Expected output: + +```html + + + +Welcome to nginx! + + + +

Welcome to nginx!

+

If you see this page, the nginx web server is successfully installed and +working. Further configuration is required.

+ +

For online documentation and support please refer to +nginx.org.
+Commercial support is available at +nginx.com.

+ +

Thank you for using nginx.

+ + +``` + +Now that you have successfully updated your stack, you'll destroy the resources. {{< get-started-stepper >}} diff --git a/content/docs/iac/get-started/kubernetes/review-project.md b/content/docs/iac/get-started/kubernetes/review-project.md deleted file mode 100644 index 60e73ad5cd54..000000000000 --- a/content/docs/iac/get-started/kubernetes/review-project.md +++ /dev/null @@ -1,313 +0,0 @@ ---- -title_tag: Review the New Project | Kubernetes -meta_desc: This page provides an overview on how to review a new Kubernetes project. -title: Review project -h1: "Pulumi & Kubernetes: Review project" -weight: 4 -menu: - iac: - name: Review project - identifier: kubernetes-get-started.review-project - parent: kubernetes-get-started - weight: 4 - -aliases: - - /docs/quickstart/kubernetes/review-project/ ---- - -Let's review some of the generated project files: - -{{% choosable language "typescript,python,go,csharp,java" %}} - -- `Pulumi.yaml` defines the [project](/docs/concepts/projects/). - -{{% /choosable %}} - -{{% choosable language yaml %}} - -- `Pulumi.yaml` defines both the [project](/docs/concepts/projects/) and the program that manages your stack resources. - -{{% /choosable %}} - -- `Pulumi.dev.yaml` contains [configuration](/docs/concepts/config/) values for the [stack](/docs/concepts/stack/) we initialized. - -{{% choosable language java %}} - -- `src/main/java/myproject` defines the project's Java package root. - -{{% /choosable %}} - -{{% choosable language python %}} - -- `__main__.py` is the Pulumi program that defines your stack resources. - -{{% /choosable %}} - -{{% choosable language "typescript,go,csharp,java" %}} - -- {{< langfile >}} is the Pulumi program that defines your stack resources. - -{{% /choosable %}} - -Let's examine {{< langfile >}}. - -{{< chooser language "typescript,python,go,csharp,java,yaml" / >}} - -{{% choosable language typescript %}} - -```typescript -import * as k8s from "@pulumi/kubernetes"; - -const appLabels = { app: "nginx" }; -const deployment = new k8s.apps.v1.Deployment("nginx", { - spec: { - selector: { matchLabels: appLabels }, - replicas: 1, - template: { - metadata: { labels: appLabels }, - spec: { containers: [{ name: "nginx", image: "nginx" }] } - } - } -}); -export const name = deployment.metadata.name; -``` - -{{% /choosable %}} -{{% choosable language python %}} - -```python -"""A Kubernetes Python Pulumi program""" - -import pulumi -from pulumi_kubernetes.apps.v1 import Deployment, DeploymentSpecArgs -from pulumi_kubernetes.meta.v1 import LabelSelectorArgs, ObjectMetaArgs -from pulumi_kubernetes.core.v1 import ContainerArgs, PodSpecArgs, PodTemplateSpecArgs - -app_labels = { "app": "nginx" } - -deployment = Deployment( - "nginx", - spec={ - "selector": { "match_labels": app_labels }, - "replicas": 1, - "template": { - "metadata": { "labels": app_labels }, - "spec": { "containers": [{ "name": "nginx", "image": "nginx" }] } - }, - }) - -pulumi.export("name", deployment.metadata["name"]) -``` - -{{% /choosable %}} -{{% choosable language go %}} - -```go -package main - -import ( - appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1" - corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1" - metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1" - "github.com/pulumi/pulumi/sdk/v3/go/pulumi" -) - -func main() { - pulumi.Run(func(ctx *pulumi.Context) error { - - appLabels := pulumi.StringMap{ - "app": pulumi.String("nginx"), - } - deployment, err := appsv1.NewDeployment(ctx, "app-dep", &appsv1.DeploymentArgs{ - Spec: appsv1.DeploymentSpecArgs{ - Selector: &metav1.LabelSelectorArgs{ - MatchLabels: appLabels, - }, - Replicas: pulumi.Int(1), - Template: &corev1.PodTemplateSpecArgs{ - Metadata: &metav1.ObjectMetaArgs{ - Labels: appLabels, - }, - Spec: &corev1.PodSpecArgs{ - Containers: corev1.ContainerArray{ - corev1.ContainerArgs{ - Name: pulumi.String("nginx"), - Image: pulumi.String("nginx"), - }}, - }, - }, - }, - }) - if err != nil { - return err - } - - ctx.Export("name", deployment.Metadata.Name()) - - return nil - }) -} - -``` - -{{% /choosable %}} -{{% choosable language csharp %}} - -```csharp -using Pulumi; -using Pulumi.Kubernetes.Types.Inputs.Core.V1; -using Pulumi.Kubernetes.Types.Inputs.Apps.V1; -using Pulumi.Kubernetes.Types.Inputs.Meta.V1; -using System.Collections.Generic; - -return await Deployment.RunAsync(() => -{ - var appLabels = new InputMap - { - { "app", "nginx" } - }; - - var deployment = new Pulumi.Kubernetes.Apps.V1.Deployment("nginx", new DeploymentArgs - { - Spec = new DeploymentSpecArgs - { - Selector = new LabelSelectorArgs - { - MatchLabels = appLabels - }, - Replicas = 1, - Template = new PodTemplateSpecArgs - { - Metadata = new ObjectMetaArgs - { - Labels = appLabels - }, - Spec = new PodSpecArgs - { - Containers = - { - new ContainerArgs - { - Name = "nginx", - Image = "nginx", - Ports = - { - new ContainerPortArgs - { - ContainerPortValue = 80 - } - } - } - } - } - } - } - }); - - // export the deployment name - return new Dictionary - { - ["name"] = deployment.Metadata.Apply(m => m.Name) - }; -}); -``` - -{{% /choosable %}} - -{{% choosable language java %}} - -```java -package myproject; - -import com.pulumi.Pulumi; -import com.pulumi.kubernetes.apps_v1.Deployment; -import com.pulumi.kubernetes.apps_v1.DeploymentArgs; -import com.pulumi.kubernetes.apps_v1.inputs.DeploymentSpecArgs; -import com.pulumi.kubernetes.core_v1.inputs.ContainerArgs; -import com.pulumi.kubernetes.core_v1.inputs.ContainerPortArgs; -import com.pulumi.kubernetes.core_v1.inputs.PodSpecArgs; -import com.pulumi.kubernetes.core_v1.inputs.PodTemplateSpecArgs; -import com.pulumi.kubernetes.meta_v1.inputs.LabelSelectorArgs; -import com.pulumi.kubernetes.meta_v1.inputs.ObjectMetaArgs; - -import java.util.Map; - -public class App { - public static void main(String[] args) { - Pulumi.run(ctx -> { - var labels = Map.of("app", "nginx"); - - var deployment = new Deployment("nginx", DeploymentArgs.builder() - .spec(DeploymentSpecArgs.builder() - .selector(LabelSelectorArgs.builder() - .matchLabels(labels) - .build()) - .replicas(1) - .template(PodTemplateSpecArgs.builder() - .metadata(ObjectMetaArgs.builder() - .labels(labels) - .build()) - .spec(PodSpecArgs.builder() - .containers(ContainerArgs.builder() - .name("nginx") - .image("nginx") - .ports(ContainerPortArgs.builder() - .containerPort(80) - .build()) - .build()) - .build()) - .build()) - - .build()) - .build()); - - var name = deployment.metadata() - .applyValue(m -> m.orElseThrow().name().orElse("")); - - ctx.export("name", name); - }); - } -} -``` - -{{% /choosable %}} - -{{% choosable language yaml %}} - -```yaml -name: quickstart -runtime: yaml -description: A minimal Kubernetes Pulumi YAML program - -variables: - appLabels: - app: nginx - -resources: - deployment: - name: nginx - type: kubernetes:apps/v1:Deployment - properties: - spec: - selector: - matchLabels: ${appLabels} - replicas: 1 - template: - metadata: - labels: ${appLabels} - spec: - containers: - - name: nginx - image: nginx - -outputs: - name: ${deployment.metadata.name} -``` - -{{% /choosable %}} - -This Pulumi program creates an NGINX deployment and exports the name of the deployment. - -Next, we'll deploy the stack. - -{{< get-started-stepper >}} diff --git a/layouts/shortcodes/get-started-install-body.html b/layouts/shortcodes/get-started-install-body.html new file mode 100644 index 000000000000..11bff9d7ac37 --- /dev/null +++ b/layouts/shortcodes/get-started-install-body.html @@ -0,0 +1,5 @@ +

Install Pulumi

+ +

Download and install Pulumi for your platform:

+ +{{ .Page.RenderString "{{< install-pulumi >}}\n{{% notes info %}}\nAll Windows examples in this tutorial assume you are running in PowerShell.\n{{% /notes %}}\n{{< /install-pulumi >}}" }} diff --git a/static/images/getting-started/console-update.png b/static/images/getting-started/console-update.png index 3cd4b0c6a4bc..00ecfde1d68c 100644 Binary files a/static/images/getting-started/console-update.png and b/static/images/getting-started/console-update.png differ