From 0ae4ddb3663f90aeb4fac9642f6385fd0356f6ec Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Fri, 27 Sep 2019 16:17:24 +0200 Subject: [PATCH] Adding more Python examples (#402) * Adding the DigitalOcean Kubernets example in Python * Adding the DigitalOcean Loadbalancer example in Python * Enabling the new DigitalOcean python examples in the test run * Adding gcp-py-serverless-raw example * Enabling gcp-py-instance-nginx test * Adding azure-py-vm-scaleset * Adding azure-py-appservice * Adding azure-py-appservice-docker * Adding azure-py-hdinsight-spark * Add azure-py-functions-raw * Add azure-py-aks-multicluster * Add azure-py-keyvault-rbac * Fixing up the azure-py-aks test to not continually use the same name * Adding customTimeouts to aws-ts-pulumi-miniflux as destroy times out * Adding aws-py-resources * Add aws-py-appsync * Add aws-py-stackreference * Review changes --- aws-py-appsync/.gitignore | 2 + aws-py-appsync/Pulumi.yaml | 8 + aws-py-appsync/README.md | 67 ++++++ aws-py-appsync/__main__.py | 131 +++++++++++ aws-py-appsync/requirements.txt | 2 + aws-py-resources/.gitignore | 2 + aws-py-resources/Pulumi.yaml | 8 + aws-py-resources/README.md | 23 ++ aws-py-resources/__main__.py | 205 ++++++++++++++++++ aws-py-resources/requirements.txt | 2 + aws-py-stackreference/README.md | 205 ++++++++++++++++++ aws-py-stackreference/company/.gitignore | 2 + aws-py-stackreference/company/Pulumi.yaml | 8 + aws-py-stackreference/company/__main__.py | 12 + .../company/requirements.txt | 2 + aws-py-stackreference/department/.gitignore | 2 + aws-py-stackreference/department/Pulumi.yaml | 8 + aws-py-stackreference/department/__main__.py | 12 + .../department/requirements.txt | 2 + aws-py-stackreference/team/.gitignore | 2 + aws-py-stackreference/team/Pulumi.yaml | 15 ++ aws-py-stackreference/team/__main__.py | 32 +++ aws-py-stackreference/team/requirements.txt | 2 + aws-ts-pulumi-miniflux/index.ts | 6 + aws-ts-resources/index.ts | 49 ----- aws-ts-stackreference/README.md | 2 +- azure-py-aks-multicluster/.gitignore | 1 + azure-py-aks-multicluster/Pulumi.yaml | 15 ++ azure-py-aks-multicluster/README.md | 81 +++++++ azure-py-aks-multicluster/__main__.py | 56 +++++ azure-py-aks-multicluster/requirements.txt | 4 + azure-py-aks/README.md | 1 - azure-py-aks/__main__.py | 5 +- azure-py-appservice-docker/.gitignore | 1 + azure-py-appservice-docker/Pulumi.yaml | 8 + azure-py-appservice-docker/README.md | 56 +++++ azure-py-appservice-docker/__main__.py | 33 +++ azure-py-appservice-docker/requirements.txt | 2 + azure-py-appservice/.gitignore | 1 + azure-py-appservice/Pulumi.yaml | 11 + azure-py-appservice/README.md | 65 ++++++ azure-py-appservice/__main__.py | 109 ++++++++++ azure-py-appservice/requirements.txt | 3 + azure-py-appservice/wwwroot/index.html | 5 + azure-py-functions-raw/.gitignore | 1 + azure-py-functions-raw/Pulumi.yaml | 10 + azure-py-functions-raw/README.md | 70 ++++++ azure-py-functions-raw/__main__.py | 83 +++++++ azure-py-functions-raw/dotnet/HelloDotnet.cs | 33 +++ .../dotnet/functionapp-dotnet.csproj | 15 ++ azure-py-functions-raw/dotnet/host.json | 3 + azure-py-functions-raw/requirements.txt | 2 + azure-py-hdinsight-spark/.gitignore | 1 + azure-py-hdinsight-spark/Pulumi.yaml | 14 ++ azure-py-hdinsight-spark/README.md | 53 +++++ azure-py-hdinsight-spark/__main__.py | 62 ++++++ azure-py-hdinsight-spark/requirements.txt | 2 + azure-py-msi-keyvault-rbac/.gitignore | 1 + azure-py-msi-keyvault-rbac/Pulumi.yaml | 10 + azure-py-msi-keyvault-rbac/README.md | 74 +++++++ azure-py-msi-keyvault-rbac/__main__.py | 185 ++++++++++++++++ azure-py-msi-keyvault-rbac/requirements.txt | 3 + azure-py-msi-keyvault-rbac/webapp/Program.cs | 23 ++ azure-py-msi-keyvault-rbac/webapp/Reader.cs | 59 +++++ azure-py-msi-keyvault-rbac/webapp/Startup.cs | 44 ++++ .../webapp/webapp.csproj | 15 ++ azure-py-vm-scaleset/.gitignore | 1 + azure-py-vm-scaleset/Pulumi.yaml | 8 + azure-py-vm-scaleset/README.md | 74 +++++++ azure-py-vm-scaleset/__main__.py | 121 +++++++++++ azure-py-vm-scaleset/requirements.txt | 3 + azure-ts-hdinsight-spark/index.ts | 10 +- digitalocean-py-k8s/.gitignore | 2 + digitalocean-py-k8s/Pulumi.yaml | 3 + digitalocean-py-k8s/README.md | 118 ++++++++++ digitalocean-py-k8s/__main__.py | 59 +++++ digitalocean-py-k8s/requirements.txt | 3 + .../.gitignore | 2 + .../Pulumi.yaml | 3 + .../README.md | 71 ++++++ .../__main__.py | 43 ++++ .../requirements.txt | 2 + gcp-py-serverless-raw/.gitignore | 2 + gcp-py-serverless-raw/Pulumi.yaml | 9 + gcp-py-serverless-raw/README.md | 37 ++++ gcp-py-serverless-raw/__main__.py | 42 ++++ gcp-py-serverless-raw/gofunc/function.go | 12 + gcp-py-serverless-raw/pythonfunc/main.py | 6 + gcp-py-serverless-raw/requirements.txt | 2 + misc/test/examples_test.go | 150 +++++++++++-- 90 files changed, 2731 insertions(+), 83 deletions(-) create mode 100644 aws-py-appsync/.gitignore create mode 100644 aws-py-appsync/Pulumi.yaml create mode 100644 aws-py-appsync/README.md create mode 100644 aws-py-appsync/__main__.py create mode 100644 aws-py-appsync/requirements.txt create mode 100644 aws-py-resources/.gitignore create mode 100644 aws-py-resources/Pulumi.yaml create mode 100644 aws-py-resources/README.md create mode 100644 aws-py-resources/__main__.py create mode 100644 aws-py-resources/requirements.txt create mode 100644 aws-py-stackreference/README.md create mode 100644 aws-py-stackreference/company/.gitignore create mode 100644 aws-py-stackreference/company/Pulumi.yaml create mode 100644 aws-py-stackreference/company/__main__.py create mode 100644 aws-py-stackreference/company/requirements.txt create mode 100644 aws-py-stackreference/department/.gitignore create mode 100644 aws-py-stackreference/department/Pulumi.yaml create mode 100644 aws-py-stackreference/department/__main__.py create mode 100644 aws-py-stackreference/department/requirements.txt create mode 100644 aws-py-stackreference/team/.gitignore create mode 100644 aws-py-stackreference/team/Pulumi.yaml create mode 100644 aws-py-stackreference/team/__main__.py create mode 100644 aws-py-stackreference/team/requirements.txt create mode 100644 azure-py-aks-multicluster/.gitignore create mode 100644 azure-py-aks-multicluster/Pulumi.yaml create mode 100644 azure-py-aks-multicluster/README.md create mode 100644 azure-py-aks-multicluster/__main__.py create mode 100644 azure-py-aks-multicluster/requirements.txt create mode 100644 azure-py-appservice-docker/.gitignore create mode 100644 azure-py-appservice-docker/Pulumi.yaml create mode 100644 azure-py-appservice-docker/README.md create mode 100644 azure-py-appservice-docker/__main__.py create mode 100644 azure-py-appservice-docker/requirements.txt create mode 100644 azure-py-appservice/.gitignore create mode 100644 azure-py-appservice/Pulumi.yaml create mode 100644 azure-py-appservice/README.md create mode 100644 azure-py-appservice/__main__.py create mode 100644 azure-py-appservice/requirements.txt create mode 100644 azure-py-appservice/wwwroot/index.html create mode 100644 azure-py-functions-raw/.gitignore create mode 100644 azure-py-functions-raw/Pulumi.yaml create mode 100644 azure-py-functions-raw/README.md create mode 100644 azure-py-functions-raw/__main__.py create mode 100644 azure-py-functions-raw/dotnet/HelloDotnet.cs create mode 100644 azure-py-functions-raw/dotnet/functionapp-dotnet.csproj create mode 100644 azure-py-functions-raw/dotnet/host.json create mode 100644 azure-py-functions-raw/requirements.txt create mode 100644 azure-py-hdinsight-spark/.gitignore create mode 100644 azure-py-hdinsight-spark/Pulumi.yaml create mode 100644 azure-py-hdinsight-spark/README.md create mode 100644 azure-py-hdinsight-spark/__main__.py create mode 100644 azure-py-hdinsight-spark/requirements.txt create mode 100644 azure-py-msi-keyvault-rbac/.gitignore create mode 100644 azure-py-msi-keyvault-rbac/Pulumi.yaml create mode 100644 azure-py-msi-keyvault-rbac/README.md create mode 100644 azure-py-msi-keyvault-rbac/__main__.py create mode 100644 azure-py-msi-keyvault-rbac/requirements.txt create mode 100644 azure-py-msi-keyvault-rbac/webapp/Program.cs create mode 100644 azure-py-msi-keyvault-rbac/webapp/Reader.cs create mode 100644 azure-py-msi-keyvault-rbac/webapp/Startup.cs create mode 100644 azure-py-msi-keyvault-rbac/webapp/webapp.csproj create mode 100644 azure-py-vm-scaleset/.gitignore create mode 100644 azure-py-vm-scaleset/Pulumi.yaml create mode 100644 azure-py-vm-scaleset/README.md create mode 100644 azure-py-vm-scaleset/__main__.py create mode 100644 azure-py-vm-scaleset/requirements.txt create mode 100644 digitalocean-py-k8s/.gitignore create mode 100644 digitalocean-py-k8s/Pulumi.yaml create mode 100644 digitalocean-py-k8s/README.md create mode 100644 digitalocean-py-k8s/__main__.py create mode 100644 digitalocean-py-k8s/requirements.txt create mode 100644 digitalocean-py-loadbalanced-droplets/.gitignore create mode 100644 digitalocean-py-loadbalanced-droplets/Pulumi.yaml create mode 100644 digitalocean-py-loadbalanced-droplets/README.md create mode 100644 digitalocean-py-loadbalanced-droplets/__main__.py create mode 100644 digitalocean-py-loadbalanced-droplets/requirements.txt create mode 100644 gcp-py-serverless-raw/.gitignore create mode 100644 gcp-py-serverless-raw/Pulumi.yaml create mode 100644 gcp-py-serverless-raw/README.md create mode 100644 gcp-py-serverless-raw/__main__.py create mode 100644 gcp-py-serverless-raw/gofunc/function.go create mode 100644 gcp-py-serverless-raw/pythonfunc/main.py create mode 100644 gcp-py-serverless-raw/requirements.txt diff --git a/aws-py-appsync/.gitignore b/aws-py-appsync/.gitignore new file mode 100644 index 000000000..a3807e5bd --- /dev/null +++ b/aws-py-appsync/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/aws-py-appsync/Pulumi.yaml b/aws-py-appsync/Pulumi.yaml new file mode 100644 index 000000000..c8d54ab40 --- /dev/null +++ b/aws-py-appsync/Pulumi.yaml @@ -0,0 +1,8 @@ +name: aws-py-appsync +runtime: python +description: Basic example of defining an AWS AppSync endpoint from Pulumi in Python +template: + config: + aws:region: + description: The AWS region to deploy into + default: us-east-2 diff --git a/aws-py-appsync/README.md b/aws-py-appsync/README.md new file mode 100644 index 000000000..d313714d3 --- /dev/null +++ b/aws-py-appsync/README.md @@ -0,0 +1,67 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# GraphQL Endpoint in AWS AppSync + +This example shows how to setup a basic GraphQL endpoint in AWS AppSync. The endpoint contains one query and one mutation that get and put items to a Dynamo DB table. + +## Deploying and running the Pulumi App + +1. Create a new stack: + + ```bash + $ pulumi stack init dev + ``` + +1. Set the AWS region: + + ``` + $ pulumi config set aws:region us-east-2 + ``` + +1. Create a Python virtualenv, activate it, and install dependencies: + + This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + + ``` + $ virtualenv -p python3 venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Run `pulumi up` to preview and deploy changes: + + ``` + $ pulumi up + Previewing update (dev): + ... + + Updating (dev): + ... + Resources: + + 10 created + Duration: 20s + ``` + +1. Check the deployed GraphQL endpoint: + + ``` + $ pulumi stack output endpoint + https://***.appsync-api.us-east-2.amazonaws.com/graphql + $ pulumi stack output key + ***sensitivekey*** + $ curl -XPOST -H "Content-Type:application/graphql" -H "x-api-key:$(pulumi stack output key)" -d '{ "query": "mutation AddTenant { addTenant(id: \"123\", name: \"FirstCorp\") { id name } }" }' "$(pulumi stack output endpoint)" + { + "data": { + "addTenant": { + "id": "123", + "name": "FirstCorp" + } + } + } + ``` + +## Clean up + +1. Run `pulumi destroy` to tear down all resources. + +1. To delete the stack itself, run `pulumi stack rm`. Note that this command deletes all deployment history from the Pulumi Console. diff --git a/aws-py-appsync/__main__.py b/aws-py-appsync/__main__.py new file mode 100644 index 000000000..2f45058bc --- /dev/null +++ b/aws-py-appsync/__main__.py @@ -0,0 +1,131 @@ +import json +from pulumi import export +from pulumi_aws import dynamodb, iam, appsync + +## Dynamo DB table to hold data for the GraphQL endpoint +table = dynamodb.Table( + "tenants", + name="Tenant", + hash_key="id", + attributes=[{ + "name": "id", + "type": "S" + }], + read_capacity=1, + write_capacity=1) + +## Create IAM role and policy wiring +role = iam.Role( + "iam-role", + assume_role_policy=json.dumps({ + "Version": "2012-10-17", + "Statement": [{ + "Action": "sts:AssumeRole", + "Principal": { + "Service": "appsync.amazonaws.com" + }, + "Effect": "Allow", + }] + })) + +policy = iam.Policy( + "iam-policy", + policy=table.arn.apply(lambda arn: json.dumps({ + "Version": "2012-10-17", + "Statement": [{ + "Action": [ + "dynamodb:PutItem", + "dynamodb:GetItem" + ], + "Effect": "Allow", + "Resource": [arn] + }] + }))) + +attachment = iam.RolePolicyAttachment( + "iam-policy-attachment", + role=role, + policy_arn=policy.arn) + +## GraphQL Schema +schema = """ +type Query { + getTenantById(id: ID!): Tenant + } + + type Mutation { + addTenant(id: ID!, name: String!): Tenant! + } + + type Tenant { + id: ID! + name: String + } + + schema { + query: Query + mutation: Mutation + } +""" + +## Create API accessible with a key +api = appsync.GraphQLApi( + "key", + authentication_type="API_KEY", + schema=schema +) + +api_key = appsync.ApiKey( + "key", + api_id=api.id) + +## Link a data source to the Dynamo DB Table +data_source = appsync.DataSource( + "tenants-ds", + name="TenantsDataSource", + api_id=api.id, + type="AMAZON_DYNAMODB", + dynamodb_config={ + "table_name": table.name + }, + service_role_arn=role.arn) + +## A resolver for the [getTenantById] query +get_resolver = appsync.Resolver( + "get-resolver", + api_id=api.id, + data_source=data_source.name, + type="Query", + field="getTenantById", + request_template="""{ + "version": "2017-02-28", + "operation": "GetItem", + "key": { + "id": $util.dynamodb.toDynamoDBJson($ctx.args.id), + } + } + """, + response_template="$util.toJson($ctx.result)") + +## A resolver for the [addTenant] mutation +add_resolver = appsync.Resolver( + "add-resolver", + api_id=api.id, + data_source=data_source.name, + type="Mutation", + field="addTenant", + request_template="""{ + "version" : "2017-02-28", + "operation" : "PutItem", + "key" : { + "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) + }, + "attributeValues" : { + "name": $util.dynamodb.toDynamoDBJson($ctx.args.name) + } + } + """, + response_template="$util.toJson($ctx.result)") + +export("endpoint", api.uris["GRAPHQL"]) +export("key", api_key.key) diff --git a/aws-py-appsync/requirements.txt b/aws-py-appsync/requirements.txt new file mode 100644 index 000000000..546a93b29 --- /dev/null +++ b/aws-py-appsync/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=1.0.0 +pulumi-aws>=1.0.0 diff --git a/aws-py-resources/.gitignore b/aws-py-resources/.gitignore new file mode 100644 index 000000000..a3807e5bd --- /dev/null +++ b/aws-py-resources/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/aws-py-resources/Pulumi.yaml b/aws-py-resources/Pulumi.yaml new file mode 100644 index 000000000..de7ed8827 --- /dev/null +++ b/aws-py-resources/Pulumi.yaml @@ -0,0 +1,8 @@ +name: aws-py-resources +description: A Pulumi program that demonstrates creating various AWS resources in PYthon +runtime: python +template: + config: + aws:region: + description: The AWS region to deploy into + default: us-east-2 diff --git a/aws-py-resources/README.md b/aws-py-resources/README.md new file mode 100644 index 000000000..c96f6d5f8 --- /dev/null +++ b/aws-py-resources/README.md @@ -0,0 +1,23 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# AWS Resources + +A Pulumi program that demonstrates creating various AWS resources in Python + +```bash +# Create and configure a new stack +$ pulumi stack init dev +$ pulumi config set aws:region us-east-2 + +# Install dependencies +$ virtualenv -p python3 venv +$ source venv/bin/activate +$ pip3 install -r requirements.txt + +# Preview and run the deployment +$ pulumi up + +# Remove the app +$ pulumi destroy +$ pulumi stack rm +``` diff --git a/aws-py-resources/__main__.py b/aws-py-resources/__main__.py new file mode 100644 index 000000000..071105fe1 --- /dev/null +++ b/aws-py-resources/__main__.py @@ -0,0 +1,205 @@ +import json +from pulumi_aws import cloudwatch, sns, dynamodb, ec2, ecr, ecs, iam, kinesis, sqs + +## CloudWatch +logins_topic = sns.Topic("myloginstopic") + +event_rule = cloudwatch.EventRule( + "myeventrule", + event_pattern=json.dumps({ + "detail-type": [ + "AWS Console Sign In via CloudTrail" + ] + })) + +event_target = cloudwatch.EventTarget( + "myeventtarget", + rule=event_rule.name, + target_id="SendToSNS", + arn=logins_topic.arn) + +log_group=cloudwatch.LogGroup("myloggroup") + +log_metric_filter = cloudwatch.LogMetricFilter( + "mylogmetricfilter", + pattern="", + log_group_name=log_group.name, + metric_transformation={ + "name": "EventCount", + "namespace": "YourNamespace", + "value": 1, + }) + +log_stream = cloudwatch.LogStream( + "mylogstream", + log_group_name=log_group.name) + +metric_alart = cloudwatch.MetricAlarm( + "mymetricalarm", + comparison_operator="GreaterThanOrEqualToThreshold", + evaluation_periods=2, + metric_name="CPUUtilization", + namespace="AWS/EC2", + period=120, + statistic="Average", + threshold=80, + alarm_description="This metric monitors ec2 cpu utilization") + +## DynamoDB +db = dynamodb.Table( + "mytable", + attributes=[{ + "name": "Id", + "type": "S" + }], + hash_key="Id", + read_capacity=1, + write_capacity=1) + +## EC2 +eip = ec2.Eip("myeip") + +security_group = ec2.SecurityGroup( + "mysecuritygroup", + ingress=[{ + "protocol": "tcp", + "from_port": 80, + "to_port": 80, + "cidr_blocks": ["0.0.0.0/0"] + }]) + +vpc = ec2.Vpc( + "myvpc", + cidr_block="10.0.0.0/16") + +igw = ec2.InternetGateway( + "myinternetgateway", + vpc_id=vpc.id) + +public_route_table = ec2.RouteTable( + "myroutetable", + routes=[{ + "cidr_block": "0.0.0.0/0", + "gateway_id": igw.id + }], + vpc_id=vpc.id) + +## ECR + +repository = ecr.Repository("myrepository") + +repository_policy = ecr.RepositoryPolicy( + "myrepositorypolicy", + repository=repository.id, + policy=json.dumps({ + "Version": "2012-10-17", + "Statement": [{ + "Sid": "new policy", + "Effect": "Allow", + "Principal": "*", + "Action": [ + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + "ecr:BatchCheckLayerAvailability", + "ecr:PutImage", + "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", + "ecr:CompleteLayerUpload", + "ecr:DescribeRepositories", + "ecr:GetRepositoryPolicy", + "ecr:ListImages", + "ecr:DeleteRepository", + "ecr:BatchDeleteImage", + "ecr:SetRepositoryPolicy", + "ecr:DeleteRepositoryPolicy", + ] + }] + }) +) + +lifecycle_policy = ecr.LifecyclePolicy( + "mylifecyclepolicy", + repository=repository.id, + policy=json.dumps({ + "rules": [{ + "rulePriority": 1, + "description": "Expire images older than 14 days", + "selection": { + "tagStatus": "untagged", + "countType": "sinceImagePushed", + "countUnit": "days", + "countNumber": 14 + }, + "action": { + "type": "expire" + } + }] + }) +) + +## ECS + +cluster = ecs.Cluster("mycluster") + +role = iam.Role( + "myrole", + assume_role_policy=json.dumps({ + "Version": "2012-10-17", + "Statement": [{ + "Action": "sts:AssumeRole", + "Principal": { + "Service": "ec2.amazonaws.com" + }, + "Effect": "Allow", + "Sid": "" + }] + })) + +role_policy = iam.RolePolicy( + "myrolepolicy", + role=role.id, + policy=json.dumps({ + "Version": "2012-10-17", + "Statement": [{ + "Action": ["ec2:Describe*"], + "Effect": "Allow", + "Resource": "*" + }] + })) + +policy = iam.Policy( + "mypolicy", + policy=json.dumps({ + "Version": "2012-10-17", + "Statement": [{ + "Action": ["ec2:Describe*"], + "Effect": "Allow", + "Resource": "*" + }] + })) + +role_policy_attachment = iam.RolePolicyAttachment( + "myrolepolicyattachment", + role=role.id, + policy_arn=policy.arn) + +user = iam.User("myuser") + +group = iam.Group("mygroup") + +## Kinesis +stream = kinesis.Stream( + "mystream", + shard_count=1) + +## SQS +queue = sqs.Queue("myqueue") + +## SNS +topic = sns.Topic("mytopic") + +topic_subscription = sns.TopicSubscription( + "mytopicsubscription", + topic=topic, + protocol="sqs", + endpoint=queue.arn) diff --git a/aws-py-resources/requirements.txt b/aws-py-resources/requirements.txt new file mode 100644 index 000000000..546a93b29 --- /dev/null +++ b/aws-py-resources/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=1.0.0 +pulumi-aws>=1.0.0 diff --git a/aws-py-stackreference/README.md b/aws-py-stackreference/README.md new file mode 100644 index 000000000..2f03b9f1c --- /dev/null +++ b/aws-py-stackreference/README.md @@ -0,0 +1,205 @@ +# StackReference Example + +This example creates a "team" EC2 Instance with tags set from _upstream_ "company" and "department" +stacks via [StackReference](https://www.pulumi.com/docs/intro/concepts/organizing-stacks-projects/#inter-stack-dependencies). + +``` +/** + * company + * └─ department + * └─ team + */ +``` + +## Getting Started + +1. Change directory to `company` and install dependencies. + + ```bash + $ cd company + ```` + +1. Create a Python virtualenv, activate it, and install dependencies: + + ``` + $ virtualenv -p python3 venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Create a new stack: + + ```bash + $ pulumi stack init dev + ``` + +1. Set the required configuration variables: + + ```bash + $ pulumi config set companyName 'ACME Widget Company' + ``` + +1. Deploy everything with the `pulumi up` command. + + ```bash + $ pulumi up + Previewing update (dev): + + Type Name Plan + + pulumi:pulumi:Stack aws-py-stackreference-company-dev create + + Resources: + + 1 to create + + Do you want to perform this update? yes + Updating (dev): + + Type Name Status + + pulumi:pulumi:Stack aws-py-stackreference-company-dev created + + Outputs: + companyName: "ACME Widget Company" + + Resources: + + 1 created + + Duration: 1s + + Permalink: https://app.pulumi.com/clstokes/aws-py-stackreference-company/dev/updates/1 + ``` + +1. Change directory to `department` and install dependencies. + + ```bash + $ cd ../company + ```` + +1. Create a Python virtualenv, activate it, and install dependencies: + + ``` + $ virtualenv -p python3 venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Create a new stack: + + ```bash + $ pulumi stack init dev + ``` + +1. Set the required configuration variables: + + ```bash + $ pulumi config set departmentName 'E-Commerce' + ``` + +1. Deploy everything with the `pulumi up` command. + + ```bash + $ pulumi up + Previewing update (dev): + + Type Name Plan + + pulumi:pulumi:Stack aws-py-stackreference-department-dev create + + Resources: + + 1 to create + + Do you want to perform this update? yes + Updating (dev): + + Type Name Status + + pulumi:pulumi:Stack aws-py-stackreference-department-dev created + + Outputs: + departmentName: "E-Commerce" + + Resources: + + 1 created + + Duration: 1s + + Permalink: https://app.pulumi.com/clstokes/aws-py-stackreference-department/dev/updates/1 + ``` + +1. Change directory to `team` and install dependencies. + + ```bash + $ cd ../team + ```` + +1. Create a Python virtualenv, activate it, and install dependencies: + + ``` + $ virtualenv -p python3 venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Create a new stack: + + ```bash + $ pulumi stack init dev + ``` + +1. Set the required configuration variables, replacing `YOUR_ORG` with the name of your Pulumi organization: + + ```bash + $ pulumi config set companyStack YOUR_ORG/aws-py-stackreference-company/dev + $ pulumi config set departmentStack YOUR_ORG/aws-py-stackreference-department/dev + $ pulumi config set teamName 'Frontend Dev' + $ pulumi config set aws:region us-west-2 # any valid AWS zone works + ``` + +1. Deploy everything with the `pulumi up` command. + + ```bash + $ envchain aws pulumi up + Previewing update (dev): + + Type Name Plan + + pulumi:pulumi:Stack aws-py-stackreference-team-dev create + >- ├─ pulumi:pulumi:StackReference clstokes/aws-py-stackreference-department/dev read + >- ├─ pulumi:pulumi:StackReference clstokes/aws-py-stackreference-company/dev read + + └─ aws:ec2:Instance tagged create + + Resources: + + 2 to create + + Do you want to perform this update? yes + Updating (dev): + + Type Name Status + + pulumi:pulumi:Stack aws-py-stackreference-team-dev created + >- ├─ pulumi:pulumi:StackReference clstokes/aws-py-stackreference-company/dev read + >- ├─ pulumi:pulumi:StackReference clstokes/aws-py-stackreference-department/dev read + + └─ aws:ec2:Instance tagged created + + Outputs: + instanceId : "i-0a9ede9c446503903" + instanceTags: { + Managed By: "Pulumi" + company : "ACME Widget Company" + department: "E-Commerce" + team : "Frontend Dev" + } + + Resources: + + 2 created + + Duration: 28s + + Permalink: https://app.pulumi.com/clstokes/aws-py-stackreference-team/dev/updates/1 + ``` + + +## Clean Up + +1. Once you are done, destroy all of the resources and the stack. Repeat this in each +of the `company`, `department`, and `team` directories from above that you ran `pulumi up` within. + + ```bash + $ pulumi destroy + $ pulumi stack rm + ``` diff --git a/aws-py-stackreference/company/.gitignore b/aws-py-stackreference/company/.gitignore new file mode 100644 index 000000000..a3807e5bd --- /dev/null +++ b/aws-py-stackreference/company/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/aws-py-stackreference/company/Pulumi.yaml b/aws-py-stackreference/company/Pulumi.yaml new file mode 100644 index 000000000..597793f4c --- /dev/null +++ b/aws-py-stackreference/company/Pulumi.yaml @@ -0,0 +1,8 @@ +name: aws-py-stackreference-company +runtime: python +description: An AWS Python Pulumi program demonstrating sharing configuration + via StackReference resources +template: + config: + companyName: + description: The company name to use diff --git a/aws-py-stackreference/company/__main__.py b/aws-py-stackreference/company/__main__.py new file mode 100644 index 000000000..16237c427 --- /dev/null +++ b/aws-py-stackreference/company/__main__.py @@ -0,0 +1,12 @@ +from pulumi import Config, export + +## +# company +# └─ department +# └─ team +## + +config = Config() +company_name = config.require("companyName") + +export("companyName", company_name) diff --git a/aws-py-stackreference/company/requirements.txt b/aws-py-stackreference/company/requirements.txt new file mode 100644 index 000000000..546a93b29 --- /dev/null +++ b/aws-py-stackreference/company/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=1.0.0 +pulumi-aws>=1.0.0 diff --git a/aws-py-stackreference/department/.gitignore b/aws-py-stackreference/department/.gitignore new file mode 100644 index 000000000..a3807e5bd --- /dev/null +++ b/aws-py-stackreference/department/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/aws-py-stackreference/department/Pulumi.yaml b/aws-py-stackreference/department/Pulumi.yaml new file mode 100644 index 000000000..9204167c2 --- /dev/null +++ b/aws-py-stackreference/department/Pulumi.yaml @@ -0,0 +1,8 @@ +name: aws-py-stackreference-department +runtime: python +description: An AWS TypeScript Pulumi program demonstrating sharing configuration + via StackReference resources in Python +template: + config: + departmentName: + description: The department name to use diff --git a/aws-py-stackreference/department/__main__.py b/aws-py-stackreference/department/__main__.py new file mode 100644 index 000000000..267c57eb4 --- /dev/null +++ b/aws-py-stackreference/department/__main__.py @@ -0,0 +1,12 @@ +from pulumi import Config, export + +## +# company +# └─ department +# └─ team +## + +config = Config() +department_name = config.require("departmentName") + +export("departmentName", department_name) diff --git a/aws-py-stackreference/department/requirements.txt b/aws-py-stackreference/department/requirements.txt new file mode 100644 index 000000000..546a93b29 --- /dev/null +++ b/aws-py-stackreference/department/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=1.0.0 +pulumi-aws>=1.0.0 diff --git a/aws-py-stackreference/team/.gitignore b/aws-py-stackreference/team/.gitignore new file mode 100644 index 000000000..a3807e5bd --- /dev/null +++ b/aws-py-stackreference/team/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/aws-py-stackreference/team/Pulumi.yaml b/aws-py-stackreference/team/Pulumi.yaml new file mode 100644 index 000000000..9c06186ea --- /dev/null +++ b/aws-py-stackreference/team/Pulumi.yaml @@ -0,0 +1,15 @@ +name: aws-py-stackreference-team +runtime: python +description: An AWS Python Pulumi program demonstrating sharing configuration + via StackReference resources +template: + config: + aws:region: + description: The AWS region to deploy into + default: us-west-2 + companyStack: + description: The stack to reference company properties from + departmentStack: + description: The stack to reference department properties from + teamName: + description: The team name to use diff --git a/aws-py-stackreference/team/__main__.py b/aws-py-stackreference/team/__main__.py new file mode 100644 index 000000000..e586b30b3 --- /dev/null +++ b/aws-py-stackreference/team/__main__.py @@ -0,0 +1,32 @@ +from pulumi import StackReference, Config, export +from pulumi_aws import get_ami, ec2 + +config = Config() +company_stack = StackReference(config.require("companyStack")) +department_stack = StackReference(config.require("departmentStack")) + +combines_tags = { + "department": department_stack.get_output("departmentName"), + "company": company_stack.get_output("companyName"), + "team": config.require("teamName"), + "Managed By": "Pulumi", +} + +ami_id = get_ami( + most_recent="true", + owners=["099720109477"], + filters=[ + { + "name":"name", + "values":["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"] + }] +).id + +instance = ec2.Instance( + "tagged", + instance_type="t2.medium", + ami=ami_id, + tags=combines_tags) + +export("instance_id", instance.id) +export("instance_tags", instance.tags) diff --git a/aws-py-stackreference/team/requirements.txt b/aws-py-stackreference/team/requirements.txt new file mode 100644 index 000000000..546a93b29 --- /dev/null +++ b/aws-py-stackreference/team/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=1.0.0 +pulumi-aws>=1.0.0 diff --git a/aws-ts-pulumi-miniflux/index.ts b/aws-ts-pulumi-miniflux/index.ts index 7c3f0b071..6eec66d3b 100644 --- a/aws-ts-pulumi-miniflux/index.ts +++ b/aws-ts-pulumi-miniflux/index.ts @@ -62,6 +62,12 @@ const service = new awsx.ecs.FargateService("service", { }, }, }, +}, { + customTimeouts: { + create: "20m", + update: "20m", + delete: "20m", + }, }); // Export the publicly accessible URL. diff --git a/aws-ts-resources/index.ts b/aws-ts-resources/index.ts index 9d33a35be..74841d366 100644 --- a/aws-ts-resources/index.ts +++ b/aws-ts-resources/index.ts @@ -3,18 +3,6 @@ import * as aws from "@pulumi/aws"; import * as pulumi from "@pulumi/pulumi"; -// Athena -// const databaseBucket = new aws.s3.Bucket("mydatabasebucket"); -// const database = new aws.athena.Database("mydatabase", { -// name: "mydatabase", -// bucket: databaseBucket.bucket -// }); - -// const namedQuery = new aws.athena.NamedQuery("mynamedquery", { -// database: database.id, -// query: pulumi.interpolate `SELECT * FROM ${database.id} limit 10;`, -// }); - // CloudWatch const dashboard = new aws.cloudwatch.Dashboard("mydashboard", { dashboardName: "my-dashboard", @@ -239,43 +227,6 @@ const stream = new aws.kinesis.Stream("mystream", { shardCount: 1, }); -// S3 -// const bucket = new aws.s3.Bucket("my-bucket"); - -// const bucketMetric = new aws.s3.BucketMetric("my-bucket-metric", { -// bucket: bucket.bucket -// }); - -// const bucketNotification = new aws.s3.BucketNotification("my-bucket-notification", { -// bucket: bucket.bucket -// }); - -// const bucketObject = new aws.s3.BucketObject("my-bucket-object", { -// bucket: bucket.bucket, -// content: "hello world" -// }); - -// const bucketPolicy = new aws.s3.BucketPolicy("my-bucket-policy", { -// bucket: bucket.bucket, -// policy: bucket.bucket.apply(publicReadPolicyForBucket) -// }) - -// function publicReadPolicyForBucket(bucketName: string) { -// return { -// Version: "2012-10-17", -// Statement: [{ -// Effect: "Allow", -// Principal: "*", -// Action: [ -// "s3:GetObject" -// ], -// Resource: [ -// `arn:aws:s3:::${bucketName}/*` // policy refers to bucket name explicitly -// ] -// }] -// }; -// } - // SQS const queue = new aws.sqs.Queue("myqueue"); diff --git a/aws-ts-stackreference/README.md b/aws-ts-stackreference/README.md index 7ad7cbf0e..3ace434f9 100644 --- a/aws-ts-stackreference/README.md +++ b/aws-ts-stackreference/README.md @@ -29,7 +29,7 @@ stacks via [StackReference](https://www.pulumi.com/docs/intro/concepts/organizin 1. Set the required configuration variables: ```bash - $ pulumi config set companyName 'ACME Widget Co.' + $ pulumi config set companyName 'ACME Widget Company' ``` 1. Deploy everything with the `pulumi up` command. diff --git a/azure-py-aks-multicluster/.gitignore b/azure-py-aks-multicluster/.gitignore new file mode 100644 index 000000000..0d20b6487 --- /dev/null +++ b/azure-py-aks-multicluster/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/azure-py-aks-multicluster/Pulumi.yaml b/azure-py-aks-multicluster/Pulumi.yaml new file mode 100644 index 000000000..97dc3ca58 --- /dev/null +++ b/azure-py-aks-multicluster/Pulumi.yaml @@ -0,0 +1,15 @@ +name: azure-py-aks-multicluster +runtime: python +description: Create multiple Azure Kubernetes Service (AKS) clusters in different regions and with different node counts (written in Python) +template: + config: + azure:environment: + description: The Azure environment to use (`public`, `usgovernment`, `german`, `china`) + default: public + password: + description: Your cluster password + secret: true + sshPublicKey: + description: Your SSH public key (generate with `ssh-keygen -t rsa -f key.rsa`) + location: + description: The location to use for the Azure Resource Group diff --git a/azure-py-aks-multicluster/README.md b/azure-py-aks-multicluster/README.md new file mode 100644 index 000000000..4b148b6b4 --- /dev/null +++ b/azure-py-aks-multicluster/README.md @@ -0,0 +1,81 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Multiple Azure Kubernetes Service (AKS) Clusters + +This example demonstrates creating multiple Azure Kubernetes Service (AKS) clusters in different regions and with +different node counts. Please see https://docs.microsoft.com/en-us/azure/aks/ for more information about AKS. + +# Prerequisites + +Ensure you have [downloaded and installed the Pulumi CLI](https://www.pulumi.com/docs/get-started/install/). + +We will be deploying to Azure, so you will need an Azure account. If you don't have an account, +[sign up for free here](https://azure.microsoft.com/en-us/free/). +[Follow the instructions here](https://www.pulumi.com/docs/intro/cloud-providers/azure/setup/) to connect Pulumi to your Azure account. + +# Running the Example + +> **Note**: Due to an issue in the Azure Terraform Provider (https://github.com/terraform-providers/terraform-provider-azurerm/issues/1635) the +> creation of an Azure Service Principal, which is needed to create the Kubernetes cluster (see index.ts), is delayed and may not +> be available when the cluster is created. If you get a Service Principal not found error, as a work around, you should be able to run `pulumi up` +> again, at which time the Service Principal should have been created. + +After cloning this repo, `cd` into it and run these commands. + +1. Create a new stack, which is an isolated deployment target for this example: + + ```bash + $ pulumi stack init dev + ``` + +1. Create a Python virtualenv, activate it, and install dependencies: + + This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + + ``` + $ virtualenv -p python3 venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Set the required configuration variables for this program: + + ```bash + $ pulumi config set azure:environment public + $ pulumi config set password --secret [your-cluster-password-here] + $ ssh-keygen -t rsa -f key.rsa + $ pulumi config set sshPublicKey < key.rsa.pub + ``` + +1. Deploy everything with the `pulumi up` command. This provisions all the Azure resources necessary, including + an Active Directory service principal and AKS clusters: + + ```bash + $ pulumi up + ``` + +1. After a couple minutes, your AKS clusters will be ready. The AKS cluster names will be printed as output variables + once `pulumi up` completes. + + ```bash + $ pulumi up + ... + + Outputs: + + aksClusterNames: [ + + [0]: "akscluster-east513be264" + + [1]: "akscluster-westece285c7" + ] + ... + ``` + +1. At this point, you have multiple AKS clusters running in different regions. Feel free to modify your program, and + run `pulumi up` to redeploy changes. The Pulumi CLI automatically detects what has changed and makes the minimal + edits necessary to accomplish these changes. + +1. Once you are done, you can destroy all of the resources, and the stack: + + ```bash + $ pulumi destroy + $ pulumi stack rm + ``` diff --git a/azure-py-aks-multicluster/__main__.py b/azure-py-aks-multicluster/__main__.py new file mode 100644 index 000000000..b0daed67f --- /dev/null +++ b/azure-py-aks-multicluster/__main__.py @@ -0,0 +1,56 @@ +from pulumi import Config, get_stack, export, Output +import pulumi_azuread as ad +import pulumi_random as random +from pulumi_azure import core, containerservice + +config = Config() +password = config.get_secret("password") or random.RandomPassword( + "pwd", + length=20, + special="true").result +ssh_public_key = config.require("sshPublicKey") + +resource_group=core.ResourceGroup("aksresourcegroup") + +ad_app = ad.Application("aks") + +ad_sp = ad.ServicePrincipal( + "aksSp", + application_id=ad_app.application_id) + +ad_sp_password = ad.ServicePrincipalPassword( + "aksSpPassword", + service_principal_id=ad_sp.id, + value=password, + end_date="2099-01-01T00:00:00Z") + +aks_cluster_config = [] +aks_cluster_config.append({"name": "east", "location": "eastus", "node_count": "2", "node_size": "Standard_D2_v2"}) +aks_cluster_config.append({"name": "west", "location": "westus", "node_count": "2", "node_size": "Standard_D2_v2"}) + +cluster_names = [] +for config in aks_cluster_config: + cluster = containerservice.KubernetesCluster( + "aksCluster-%s" % config["name"], + resource_group_name=resource_group.name, + linux_profile={ + "admin_username": "aksuser", + "ssh_key": { + "key_data": ssh_public_key, + }, + }, + service_principal={ + "client_id": ad_app.application_id, + "client_secret": ad_sp_password.value + }, + location=config["location"], + agent_pool_profiles=[{ + "name": "aksagentpool", + "count": config["node_count"], + "vm_size": config["node_size"], + }], + dns_prefix="sample-kube", + ) + cluster_names.append(cluster.name) + +export("aks_cluster_names", Output.all(cluster_names)) diff --git a/azure-py-aks-multicluster/requirements.txt b/azure-py-aks-multicluster/requirements.txt new file mode 100644 index 000000000..95fb8c05c --- /dev/null +++ b/azure-py-aks-multicluster/requirements.txt @@ -0,0 +1,4 @@ +pulumi>=1.0.0 +pulumi-azure>=1.0.0 +pulumi-azuread>=1.0.0 +pulumi-random>=1.0.0 diff --git a/azure-py-aks/README.md b/azure-py-aks/README.md index 4fe909ad9..a2004a53c 100644 --- a/azure-py-aks/README.md +++ b/azure-py-aks/README.md @@ -34,7 +34,6 @@ After cloning this repo, from this working directory, run these commands: 3. Set the configuration variables for this program: ```bash - $ pulumi config set prefix all_resources_will_be_prefixed_with_this_value $ pulumi config set password service_principal_password $ pulumi config set sshkey < ~/.ssh/id_rsa.pub $ # this has a default value, so you can skip it diff --git a/azure-py-aks/__main__.py b/azure-py-aks/__main__.py index 9ade742e1..711febaf4 100644 --- a/azure-py-aks/__main__.py +++ b/azure-py-aks/__main__.py @@ -10,16 +10,15 @@ # read and set config values config = pulumi.Config("azure-py-aks") -PREFIX = config.require("prefix") PASSWORD = config.require_secret("password") SSHKEY = config.require("sshkey") LOCATION = config.get("location") or "east us" # create a Resource Group and Network for all resources -resource_group = ResourceGroup("rg", name=PREFIX + "rg", location=LOCATION) +resource_group = ResourceGroup("aks-rg") # create Azure AD Application for AKS -app = Application("aks-app", name=PREFIX + "aks-app") +app = Application("aks-app") # create service principal for the application so AKS can act on behalf of the application sp = ServicePrincipal( diff --git a/azure-py-appservice-docker/.gitignore b/azure-py-appservice-docker/.gitignore new file mode 100644 index 000000000..0d20b6487 --- /dev/null +++ b/azure-py-appservice-docker/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/azure-py-appservice-docker/Pulumi.yaml b/azure-py-appservice-docker/Pulumi.yaml new file mode 100644 index 000000000..52aea96f9 --- /dev/null +++ b/azure-py-appservice-docker/Pulumi.yaml @@ -0,0 +1,8 @@ +name: azure-py-appservice-docker +runtime: python +description: Creates Azure App Service running Docker containers on Linux in Python +template: + config: + azure:environment: + description: The Azure environment to use (`public`, `usgovernment`, `german`, `china`) + default: public diff --git a/azure-py-appservice-docker/README.md b/azure-py-appservice-docker/README.md new file mode 100644 index 000000000..ec84b3b42 --- /dev/null +++ b/azure-py-appservice-docker/README.md @@ -0,0 +1,56 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Azure App Service Cunning Docker Containers on Linux + +Starting point for building web application hosted in Azure App Service from Docker images. + +The example deploys an existing image from Docker Hub + +## Running the App + +1. Create a new stack: + + ``` + $ pulumi stack init dev + ``` + +1. Login to Azure CLI (you will be prompted to do this during deployment if you forget this step): + + ``` + $ az login + ``` + +1. Create a Python virtualenv, activate it, and install dependencies: + + This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + + ``` + $ virtualenv -p python3 venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Run `pulumi up` to preview and deploy changes: + + ``` + $ pulumi up + Previewing changes: + ... + + Performing changes: + ... + Resources: + + 4 created + + Duration: 4m56s + ``` + +1. Check the deployed endpoints: + + ``` + $ pulumi stack output hello_endpoint + http://hello-app91dfea21.azurewebsites.net/hello + $ curl "$(pulumi stack output hello_endpoint)" + Hello, world! + + ``` diff --git a/azure-py-appservice-docker/__main__.py b/azure-py-appservice-docker/__main__.py new file mode 100644 index 000000000..c63b06baf --- /dev/null +++ b/azure-py-appservice-docker/__main__.py @@ -0,0 +1,33 @@ +from pulumi_azure import core, appservice, containerservice +from pulumi import export, Output + +resource_group = core.ResourceGroup("samples") + +plan = appservice.Plan( + "linux-apps", + resource_group_name=resource_group.name, + kind="Linux", + reserved="true", + sku={ + "tier": "Basic", + "size": "B1", + }) + +docker_image = "microsoft/azure-appservices-go-quickstart" + +hello_app = appservice.AppService( + "hello-app", + resource_group_name=resource_group.name, + app_service_plan_id=plan.id, + app_settings={ + "WEBSITES_ENABLE_APP_SERVICE_STORAGE": "false", + }, + site_config={ + "always_on": "true", + "linux_fx_version": "DOCKER|%s" % docker_image, + }, + https_only="true") + +export("hello_endpoint", hello_app.default_site_hostname.apply( + lambda endpoint: "https://" + endpoint + "/hello" +)) diff --git a/azure-py-appservice-docker/requirements.txt b/azure-py-appservice-docker/requirements.txt new file mode 100644 index 000000000..b0f0a4761 --- /dev/null +++ b/azure-py-appservice-docker/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=1.0.0 +pulumi-azure>=1.0.0 diff --git a/azure-py-appservice/.gitignore b/azure-py-appservice/.gitignore new file mode 100644 index 000000000..0d20b6487 --- /dev/null +++ b/azure-py-appservice/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/azure-py-appservice/Pulumi.yaml b/azure-py-appservice/Pulumi.yaml new file mode 100644 index 000000000..f8d8f8e27 --- /dev/null +++ b/azure-py-appservice/Pulumi.yaml @@ -0,0 +1,11 @@ +name: azure--pyappservice +runtime: python +description: Creates Azure App Service with SQL Database and Application Insights in Python +template: + config: + azure:environment: + description: The Azure environment to use (`public`, `usgovernment`, `german`, `china`) + default: public + sqlPassword: + description: SQL Server password (complex enough to satisfy Azure policy) + secret: true diff --git a/azure-py-appservice/README.md b/azure-py-appservice/README.md new file mode 100644 index 000000000..869cffaf2 --- /dev/null +++ b/azure-py-appservice/README.md @@ -0,0 +1,65 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Azure App Service with SQL Database and Application Insights + +Starting point for building web application hosted in Azure App Service. + +Provisions Azure SQL Database and Azure Application Insights to be used in combination +with App Service. + +## Running the App + +1. Create a new stack: + + ``` + $ pulumi stack init dev + ``` + +1. Login to Azure CLI (you will be prompted to do this during deployment if you forget this step): + + ``` + $ az login + ``` + +1. Create a Python virtualenv, activate it, and install dependencies: + + This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + + ``` + $ virtualenv -p python3 venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Define SQL Server password (make it complex enough to satisfy Azure policy): + + ``` + pulumi config set --secret sqlPassword + ``` + +1. Run `pulumi up` to preview and deploy changes: + + ``` + $ pulumi up + Previewing changes: + ... + + Performing changes: + ... + info: 10 changes performed: + + 10 resources created + Update duration: 1m14.59910109s + ``` + +1. Check the deployed website endpoint: + + ``` + $ pulumi stack output endpoint + https://azpulumi-as0ef47193.azurewebsites.net + $ curl "$(pulumi stack output endpoint)" + + +

Greetings from Azure App Service!

+ + + ``` diff --git a/azure-py-appservice/__main__.py b/azure-py-appservice/__main__.py new file mode 100644 index 000000000..606109b5a --- /dev/null +++ b/azure-py-appservice/__main__.py @@ -0,0 +1,109 @@ +from pulumi import Config, export, asset, Output +from pulumi_azure import core, storage, appservice, appinsights, sql + +username = "pulumi" + +config = Config() +pwd = config.require("sqlPassword") + +resource_group = core.ResourceGroup("appservicerg") + +storage_account = storage.Account( + "appservicesa", + resource_group_name=resource_group.name, + account_kind="StorageV2", + account_tier="Standard", + account_replication_type="LRS") + +app_service_plan = appservice.Plan( + "appservice-asp", + resource_group_name=resource_group.name, + kind="App", + sku={ + "tier": "Basic", + "size": "B1", + }) + +storage_container = storage.Container( + "appservice-c", + storage_account_name=storage_account.name, + container_access_type="private") + +blob = storage.ZipBlob( + "appservice-b", + resource_group_name=resource_group.name, + storage_account_name=storage_account.name, + storage_container_name=storage_container.name, + type="block", + content=asset.FileArchive("wwwroot")) + +account_sas=storage.get_account_sas( + connection_string=storage_account.primary_connection_string, + start="2019-01-01", + expiry="2029-01-01", + services={ + "blob": "true", + "queue": "false", + "table": "false", + "file": "false" + }, + resource_types={ + "service": "false", + "container": "false", + "object": "true" + }, + permissions={ + "read": "true", + "write": "false", + "delete": "false", + "add": "false", + "list": "false", + "create": "false", + "update": "false", + "process": "false" + }, +) + +app_insights = appinsights.Insights( + "appservice-ai", + resource_group_name=resource_group.name, + location=resource_group.location, + application_type="Web") + +sql_server = sql.SqlServer( + "appservice-sql", + resource_group_name=resource_group.name, + administrator_login=username, + administrator_login_password=pwd, + version="12.0") + +database = sql.Database( + "appservice-db", + resource_group_name=resource_group.name, + server_name=sql_server.name, + requested_service_objective_name="S0") + +signed_blob_url = Output.all(storage_account.name, storage_container.name, blob.name, account_sas.sas) \ + .apply(lambda args: f"https://{args[0]}.blob.core.windows.net/{args[1]}/{args[2]}{args[3]}") +connection_string = Output.all(sql_server.name, database.name, username, pwd) \ + .apply(lambda args: f"Server=tcp:{args[0]}.database.windows.net;initial catalog={args[1]};user ID={args[2]};password={args[3]};Min Pool Size=0;Max Pool Size=30;Persist Security Info=true;") + +app=appservice.AppService( + "appservice-as", + resource_group_name=resource_group.name, + app_service_plan_id=app_service_plan.id, + app_settings={ + "WEBSITE_RUN_FROM_ZIP": signed_blob_url, + "ApplicationInsights:InstrumentationKey": app_insights.instrumentation_key, + "APPINSIGHTS_INSTRUMENTATIONKEY": app_insights.instrumentation_key, + }, + connection_strings=[{ + "name": "db", + "type": "SQLAzure", + "value": connection_string + }] +) + +export("endpoint", app.default_site_hostname.apply( + lambda endpoint: "https://" + endpoint +)) diff --git a/azure-py-appservice/requirements.txt b/azure-py-appservice/requirements.txt new file mode 100644 index 000000000..53926ea42 --- /dev/null +++ b/azure-py-appservice/requirements.txt @@ -0,0 +1,3 @@ +pulumi>=1.0.0 +pulumi-azure>=1.0.0 +pulumi-random>=1.0.0 diff --git a/azure-py-appservice/wwwroot/index.html b/azure-py-appservice/wwwroot/index.html new file mode 100644 index 000000000..01a27656d --- /dev/null +++ b/azure-py-appservice/wwwroot/index.html @@ -0,0 +1,5 @@ + + +

Greetings from Azure App Service!

+ + \ No newline at end of file diff --git a/azure-py-functions-raw/.gitignore b/azure-py-functions-raw/.gitignore new file mode 100644 index 000000000..0d20b6487 --- /dev/null +++ b/azure-py-functions-raw/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/azure-py-functions-raw/Pulumi.yaml b/azure-py-functions-raw/Pulumi.yaml new file mode 100644 index 000000000..8c30877e9 --- /dev/null +++ b/azure-py-functions-raw/Pulumi.yaml @@ -0,0 +1,10 @@ +name: azure-py-functions-raw +runtime: python +description: Azure Functions created from raw deployment packages in C# (written in Python) +template: + config: + azure:environment: + description: The Azure environment to use (`public`, `usgovernment`, `german`, `china`) + default: public + azure:location: + description: The Azure location to deploy resources to (e.g., `eastus` or `westeurope`) diff --git a/azure-py-functions-raw/README.md b/azure-py-functions-raw/README.md new file mode 100644 index 000000000..1a21e9ae6 --- /dev/null +++ b/azure-py-functions-raw/README.md @@ -0,0 +1,70 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Azure Functions + +Azure Functions created from raw deployment packages in C#. + +C# is a precompiled language, and the deployment artifact contains compiled binaries. You will need the following tool to build this projects: + +- [.NET Core SDK](https://dotnet.microsoft.com/download) for the .NET Function App + +Please remove the corresponding resources from the program in case you don't need those runtimes. + +## Running the App + +1. Build and publish the .NET Function App project: + + ``` + $ dotnet publish dotnet + ``` + +1. Create a new stack: + + ``` + $ pulumi stack init dev + ``` + +1. Login to Azure CLI (you will be prompted to do this during deployment if you forget this step): + + ``` + $ az login + ``` + +1. Create a Python virtualenv, activate it, and install dependencies: + + This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + + ``` + $ virtualenv -p python3 venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Configure the location to deploy the resources to: + + ``` + $ pulumi config set azure:location + ``` + +1. Run `pulumi up` to preview and deploy changes: + + ``` + $ pulumi up + Previewing update (dev): + ... + + Updating (dev): + ... + Resources: + + 7 created + Duration: 2m42s + ``` + +1. Check the deployed function endpoints: + + ``` + $ pulumi stack output dotnet_endpoint + https://http-dotnet1a2d3e4d.azurewebsites.net/api/HelloDotnet?name=Pulumi + $ curl "$(pulumi stack output dotnet_endpoint)" + Hello from .NET, Pulumi + ``` diff --git a/azure-py-functions-raw/__main__.py b/azure-py-functions-raw/__main__.py new file mode 100644 index 000000000..cf8b0d9ab --- /dev/null +++ b/azure-py-functions-raw/__main__.py @@ -0,0 +1,83 @@ +from pulumi import asset, export, Output +from pulumi_azure import core, storage, appservice + +resource_group = core.ResourceGroup("windowsrg") + +httpdotnet_storage_account = storage.Account( + "httpdotnet", + account_kind="StorageV2", + account_tier="Standard", + account_replication_type="LRS", + resource_group_name=resource_group.name, +) + +httpdotnet_container=storage.Container( + "http-dotnet", + storage_account_name=httpdotnet_storage_account.name, + container_access_type="private" +) + +httpdotnet_zib_blob=storage.ZipBlob( + "http-dotnet", + resource_group_name=resource_group.name, + storage_account_name=httpdotnet_storage_account.name, + storage_container_name=httpdotnet_container.name, + type="block", + content=asset.AssetArchive({ + ".": asset.FileArchive("./dotnet/bin/Debug/netcoreapp2.1/publish") + })) + +account_sas=storage.get_account_sas( + connection_string=httpdotnet_storage_account.primary_connection_string, + start="2019-01-01", + expiry="2029-01-01", + services={ + "blob": "true", + "queue": "false", + "table": "false", + "file": "false" + }, + resource_types={ + "service": "false", + "container": "false", + "object": "true" + }, + permissions={ + "read": "true", + "write": "false", + "delete": "false", + "add": "false", + "list": "false", + "create": "false", + "update": "false", + "process": "false" + }, +) +httpdotnet_signed_blob_url = Output.all(httpdotnet_storage_account.name, httpdotnet_container.name, httpdotnet_zib_blob.name, account_sas.sas) \ + .apply(lambda args: f"https://{args[0]}.blob.core.windows.net/{args[1]}/{args[2]}{args[3]}") + +httpdotnet_plan=appservice.Plan( + "http-dotnet", + resource_group_name=resource_group.name, + kind="FunctionApp", + sku={ + "tier": "Dynamic", + "size": "Y1" + } +) + +httpdotnet_function_app=appservice.FunctionApp( + "http-dotnet", + resource_group_name=resource_group.name, + app_service_plan_id=httpdotnet_plan.id, + storage_connection_string=httpdotnet_storage_account.primary_connection_string, + version="~2", + app_settings={ + "runtime": "dotnet", + "WEBSITE_NODE_DEFAULT_VERSION": "8.11.1", + "WEBSITE_RUN_FROM_PACKAGE": httpdotnet_signed_blob_url, + }, +) + +export("dotnet_endpoint", httpdotnet_function_app.default_hostname.apply( + lambda endpoint: "https://" + endpoint + "/api/HelloDotnet?name=Pulumi")) diff --git a/azure-py-functions-raw/dotnet/HelloDotnet.cs b/azure-py-functions-raw/dotnet/HelloDotnet.cs new file mode 100644 index 000000000..a33d7ccba --- /dev/null +++ b/azure-py-functions-raw/dotnet/HelloDotnet.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace functionapp_dotnet +{ + public static class HelloDotnet + { + [FunctionName("HelloDotnet")] + public static async Task Run( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, + ILogger log) + { + log.LogInformation("C# HTTP trigger function processed a request."); + + string name = req.Query["name"]; + + string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); + dynamic data = JsonConvert.DeserializeObject(requestBody); + name = name ?? data?.name; + + return name != null + ? (ActionResult)new OkObjectResult($"Hello from .NET, {name}") + : new BadRequestObjectResult("Please pass a name on the query string or in the request body"); + } + } +} diff --git a/azure-py-functions-raw/dotnet/functionapp-dotnet.csproj b/azure-py-functions-raw/dotnet/functionapp-dotnet.csproj new file mode 100644 index 000000000..abdb51b36 --- /dev/null +++ b/azure-py-functions-raw/dotnet/functionapp-dotnet.csproj @@ -0,0 +1,15 @@ + + + netcoreapp2.1 + v2 + functionapp_dotnet + + + + + + + PreserveNewest + + + \ No newline at end of file diff --git a/azure-py-functions-raw/dotnet/host.json b/azure-py-functions-raw/dotnet/host.json new file mode 100644 index 000000000..b9f92c0de --- /dev/null +++ b/azure-py-functions-raw/dotnet/host.json @@ -0,0 +1,3 @@ +{ + "version": "2.0" +} \ No newline at end of file diff --git a/azure-py-functions-raw/requirements.txt b/azure-py-functions-raw/requirements.txt new file mode 100644 index 000000000..b0f0a4761 --- /dev/null +++ b/azure-py-functions-raw/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=1.0.0 +pulumi-azure>=1.0.0 diff --git a/azure-py-hdinsight-spark/.gitignore b/azure-py-hdinsight-spark/.gitignore new file mode 100644 index 000000000..0d20b6487 --- /dev/null +++ b/azure-py-hdinsight-spark/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/azure-py-hdinsight-spark/Pulumi.yaml b/azure-py-hdinsight-spark/Pulumi.yaml new file mode 100644 index 000000000..924e4cfed --- /dev/null +++ b/azure-py-hdinsight-spark/Pulumi.yaml @@ -0,0 +1,14 @@ +name: azure-py-hdinsight-spark +runtime: python +description: Spark on Azure HDInsight example in Python +template: + config: + azure:environment: + description: The Azure environment to use (`public`, `usgovernment`, `german`, `china`) + default: public + username: + description: Spark username + secret: true + password: + description: "Spark password (complex enough to satisfy Azure policy: 8+ chars of lowercase, uppercase, digits, and special symbols)" + secret: true diff --git a/azure-py-hdinsight-spark/README.md b/azure-py-hdinsight-spark/README.md new file mode 100644 index 000000000..edec64705 --- /dev/null +++ b/azure-py-hdinsight-spark/README.md @@ -0,0 +1,53 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Spark on Azure HDInsight + +An example Pulumi component that deploys a Spark cluster on Azure HDInsight. + +## Running the App + +1. Create a new stack: + + ``` + $ pulumi stack init dev + ``` + +1. Login to Azure CLI (you will be prompted to do this during deployment if you forget this step): + + ``` + $ az login + ``` + +1. Create a Python virtualenv, activate it, and install dependencies: + + This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + + ``` + $ virtualenv -p python3 venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Run `pulumi up` to preview and deploy changes: + + ``` + $ pulumi up + Previewing changes: + ... + + Performing changes: + ... + info: 5 changes performed: + + 5 resources created + Update duration: 15m6s + ``` + +1. Check the deployed Spark endpoint: + + ``` + $ pulumi stack output endpoint + https://myspark1234abcd.azurehdinsight.net/ + + # For instance, Jupyter notebooks are available at https://myspark1234abcd.azurehdinsight.net/jupyter/ + # Follow https://docs.microsoft.com/en-us/azure/hdinsight/spark/apache-spark-load-data-run-query to test it out + ``` diff --git a/azure-py-hdinsight-spark/__main__.py b/azure-py-hdinsight-spark/__main__.py new file mode 100644 index 000000000..e5ca036d9 --- /dev/null +++ b/azure-py-hdinsight-spark/__main__.py @@ -0,0 +1,62 @@ +from pulumi import Config, export +from pulumi_azure import core, storage, hdinsight + +config = Config() +username = config.require("username") +password = config.require_secret("password") + +resource_group = core.ResourceGroup("spark-rg") + +storage_account = storage.Account( + "sparksa", + resource_group_name=resource_group.name, + account_replication_type="LRS", + account_tier="Standard") + +storage_container = storage.Container( + "spark", + storage_account_name=storage_account.name, + container_access_type="private") + +spark_cluster = hdinsight.SparkCluster( + "myspark", + resource_group_name=resource_group.name, + cluster_version="3.6", + component_version={ + "spark": "2.3" + }, + tier="Standard", + storage_accounts=[{ + "is_default": "true", + "storage_account_key": storage_account.primary_access_key, + "storage_container_id": storage_container.id + }], + gateway={ + "enabled": "true", + "username": username, + "password": password + }, + roles={ + "head_node": { + "vm_size": "Standard_D12_v2", + "username": username, + "password": password + }, + "worker_node": { + "vm_size": "Standard_D12_v2", + "username": username, + "password": password, + "target_instance_count": "3", + }, + "zookeeper_node": { + "vm_size": "Standard_D12_v2", + "username": username, + "password": password + } + } +) + +export("endpoint", spark_cluster.https_endpoint.apply( + lambda endpoint: "https://" + endpoint +)) + diff --git a/azure-py-hdinsight-spark/requirements.txt b/azure-py-hdinsight-spark/requirements.txt new file mode 100644 index 000000000..b0f0a4761 --- /dev/null +++ b/azure-py-hdinsight-spark/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=1.0.0 +pulumi-azure>=1.0.0 diff --git a/azure-py-msi-keyvault-rbac/.gitignore b/azure-py-msi-keyvault-rbac/.gitignore new file mode 100644 index 000000000..0d20b6487 --- /dev/null +++ b/azure-py-msi-keyvault-rbac/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/azure-py-msi-keyvault-rbac/Pulumi.yaml b/azure-py-msi-keyvault-rbac/Pulumi.yaml new file mode 100644 index 000000000..9b861dd57 --- /dev/null +++ b/azure-py-msi-keyvault-rbac/Pulumi.yaml @@ -0,0 +1,10 @@ +name: azure-py-msi-keyvault-rbac +runtime: python +description: Example of managing the secrets and permissions via services and features like KeyVault, AD Managed Identity, AD RBAC in Python +template: + config: + azure:environment: + description: The Azure environment to use (`public`, `usgovernment`, `german`, `china`) + default: public + azure:location: + description: The Azure location to use (e.g., `eastus` or `westeurope`) diff --git a/azure-py-msi-keyvault-rbac/README.md b/azure-py-msi-keyvault-rbac/README.md new file mode 100644 index 000000000..748cf6cb7 --- /dev/null +++ b/azure-py-msi-keyvault-rbac/README.md @@ -0,0 +1,74 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Managing Secrets and Secure Access in Azure Applications + +[Managed identities](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/) for Azure resources provides Azure services with an automatically managed identity in Azure Active Directory (Azure AD). + +This example demostrates using a managed identity with Azure App Service to access Azure KeyVault, Azure Storage, and Azure SQL Database without passwords or secrets. + +The application consists of several parts: + +- An ASP.NET Application which reads data from a SQL Database and from a file in Blob Storage +- App Service which host the application. The application binaries are placed in Blob Storage, with Blob Url placed as a secret in Azure Key Vault +- App Service has a Managed Identity enabled +- The identify is granted access to the SQL Server, Blob Storage, and Key Vault +- No secret information is placed in App Service configuration: all access rights are derived from Active Directory + +## Running the App + +1. Create a new stack: + + ``` + $ pulumi stack init dev + ``` + +1. Login to Azure CLI (you will be prompted to do this during deployment if you forget this step): + + ``` + $ az login + ``` + +1. Create a Python virtualenv, activate it, and install dependencies: + + This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + + ``` + $ virtualenv -p python3 venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Build and publish the ASP.NET Core project: + + ``` + $ dotnet publish webapp + ``` + +1. Set an appropriate Azure location like: + + ``` + $ pulumi config set azure:location westus + ``` + +1. Run `pulumi up` to preview and deploy changes: + + ``` + $ pulumi up + Previewing changes: + ... + + Performing changes: + ... + info: 15 changes performed: + + 15 resources created + Update duration: 4m16s + ``` + +1. Check the deployed website endpoint: + + ``` + $ pulumi stack output endpoint + https://app129968b8.azurewebsites.net/ + $ curl "$(pulumi stack output endpoint)" + Hello 311378b3-16b7-4889-a8d7-2eb77478beba@50f73f6a-e8e3-46b6-969c-bf026712a650! Here is your... + ``` diff --git a/azure-py-msi-keyvault-rbac/__main__.py b/azure-py-msi-keyvault-rbac/__main__.py new file mode 100644 index 000000000..68df135a2 --- /dev/null +++ b/azure-py-msi-keyvault-rbac/__main__.py @@ -0,0 +1,185 @@ +from pulumi_azure import core, storage, sql, appservice, keyvault, role +from pulumi import export, Output, asset +import pulumi_random as random +import json, os + +def createFirewallRules(arg): + ips = arg.split(",") + for ip in ips: + rule = sql.FirewallRule( + "FR%s" % ip, + resource_group_name=resource_group.name, + start_ip_address=ip, + end_ip_address=ip, + server_name=sql_server.name + ) + +resource_group = core.ResourceGroup("resourceGroup") + +storage_account = storage.Account( + "storage", + resource_group_name=resource_group.name, + account_replication_type="LRS", + account_tier="Standard") + +container = storage.Container( + "files", + storage_account_name=storage_account.name, + container_access_type="private") + +administrator_login_password = random.RandomPassword( + "password", + length=16, + special="true", +).result + +sql_server = sql.SqlServer( + "sqlserver", + resource_group_name=resource_group.name, + administrator_login_password=administrator_login_password, + administrator_login="manualadmin", + version="12.0") + + +database = sql.Database( + "sqldb", + resource_group_name=resource_group.name, + server_name=sql_server.name, + requested_service_objective_name="S0") + +connection_string = Output.all(sql_server.name, database.name) \ + .apply(lambda args: f"Server=tcp:{args[0]}.database.windows.net;Database={args[1]};") or "1111" + +text_blob = storage.Blob( + "text", + resource_group_name=resource_group.name, + storage_account_name=storage_account.name, + storage_container_name=container.name, + type="block", + source="./README.md" +) + +app_service_plan = appservice.Plan( + "asp", + resource_group_name=resource_group.name, + kind="App", + sku={ + "tier": "Basic", + "size": "B1" + } +) + +blob = storage.ZipBlob( + "zip", + resource_group_name=resource_group.name, + storage_account_name=storage_account.name, + storage_container_name=container.name, + type="block", + content=asset.FileArchive("./webapp/bin/Debug/netcoreapp2.2/publish") +) + +client_config = core.get_client_config() +tenant_id = client_config.tenant_id +current_principal = client_config.service_principal_object_id or json.loads(os.popen("az ad signed-in-user show --query objectId").read()) + +vault = keyvault.KeyVault( + "vault", + resource_group_name=resource_group.name, + sku_name="standard", + tenant_id=tenant_id, + access_policies=[{ + "tenant_id": tenant_id, + "object_id": current_principal, + "secret_permissions": ["delete", "get", "list", "set"] + }] +) + +account_sas=storage.get_account_sas( + connection_string=storage_account.primary_connection_string, + start="2019-01-01", + expiry="2029-01-01", + services={ + "blob": "true", + "queue": "false", + "table": "false", + "file": "false" + }, + resource_types={ + "service": "false", + "container": "false", + "object": "true" + }, + permissions={ + "read": "true", + "write": "false", + "delete": "false", + "add": "false", + "list": "false", + "create": "false", + "update": "false", + "process": "false" + }, +) + +signed_blob_url = Output.all(storage_account.name, container.name, blob.name, account_sas.sas) \ + .apply(lambda args: f"https://{args[0]}.blob.core.windows.net/{args[1]}/{args[2]}{args[3]}") + +secret = keyvault.Secret( + "deployment-zip", + key_vault_id=vault.id, + value=signed_blob_url) + +secret_uri = Output.all(secret.vault_uri, secret.name, secret.version) \ + .apply(lambda args: f"{args[0]}secrets/{args[1]}/{args[2]}") + +app = appservice.AppService( + "app", + resource_group_name=resource_group.name, + app_service_plan_id=app_service_plan.id, + identity={ + "type": "SystemAssigned", + }, + app_settings={ + "WEBSITE_RUN_FROM_ZIP": secret_uri.apply(lambda args: "@Microsoft.KeyVault(SecretUri=" + args + ")"), + "StorageBlobUrl": text_blob.url + }, + connection_strings=[{ + "name": "db", + "value": connection_string, + "type": "SQLAzure" + }] +) + +## Work around a preview issue https://github.com/pulumi/pulumi-azure/issues/192 +principal_id = app.identity["principal_id"] or "11111111-1111-1111-1111-111111111111" + +policy = keyvault.AccessPolicy( + "app-policy", + key_vault_id=vault.id, + tenant_id=tenant_id, + object_id=principal_id, + secret_permissions=["get"]) + +sql_admin = sql.ActiveDirectoryAdministrator( + "adamin", + resource_group_name=resource_group.name, + tenant_id=tenant_id, + object_id=principal_id, + login="adadmin", + server_name=sql_server.name) + + +blob_permission = role.Assignment( + "readblob", + principal_id=principal_id, + role_definition_name="Storage Blob Data Reader", + scope=Output.all(storage_account.id, container.name).apply(lambda args: f"{args[0]}/blobServices/default/containers/{args[1]}") +) + +ips = app.outbound_ip_addresses.apply(createFirewallRules) + +export("endpoint", app.default_site_hostname.apply( + lambda endpoint: "https://" + endpoint +)) + + diff --git a/azure-py-msi-keyvault-rbac/requirements.txt b/azure-py-msi-keyvault-rbac/requirements.txt new file mode 100644 index 000000000..53926ea42 --- /dev/null +++ b/azure-py-msi-keyvault-rbac/requirements.txt @@ -0,0 +1,3 @@ +pulumi>=1.0.0 +pulumi-azure>=1.0.0 +pulumi-random>=1.0.0 diff --git a/azure-py-msi-keyvault-rbac/webapp/Program.cs b/azure-py-msi-keyvault-rbac/webapp/Program.cs new file mode 100644 index 000000000..96f82f5e7 --- /dev/null +++ b/azure-py-msi-keyvault-rbac/webapp/Program.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Logging; + +namespace webapp +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/azure-py-msi-keyvault-rbac/webapp/Reader.cs b/azure-py-msi-keyvault-rbac/webapp/Reader.cs new file mode 100644 index 000000000..bdc4c2f04 --- /dev/null +++ b/azure-py-msi-keyvault-rbac/webapp/Reader.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Azure.Services.AppAuthentication; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System.Data.SqlClient; +using Microsoft.Azure.Storage.Auth; +using System.IO; +using Microsoft.Azure.Storage.Blob; + +namespace webapp +{ + public class Reader + { + public Reader(IConfiguration configuration) + { + this.Configuration = configuration; + } + + private IConfiguration Configuration { get; } + + public async Task GetSqlUser() + { + var connectionString = Configuration.GetConnectionString("db"); + var token = await GetTokenAsync("database.windows.net"); + using (var conn = new SqlConnection(connectionString)) + { + conn.AccessToken = token; + await conn.OpenAsync(); + + using (var cmd = new SqlCommand("SELECT SUSER_SNAME()", conn)) + { + var result = await cmd.ExecuteScalarAsync(); + return result as string; + } + } + } + + public async Task GetBlobText() + { + string accessToken = await GetTokenAsync("storage.azure.com"); + var tokenCredential = new TokenCredential(accessToken); + var storageCredentials = new StorageCredentials(tokenCredential); + // Define the blob to read + var url = Environment.GetEnvironmentVariable("StorageBlobUrl"); + var blob = new CloudBlockBlob(new Uri(url), storageCredentials); + // Open a data stream to the blob + return await blob.DownloadTextAsync(); + } + + private static Task GetTokenAsync(string service) + { + var provider = new AzureServiceTokenProvider(); + return provider.GetAccessTokenAsync($"https://{service}/"); + } + } +} diff --git a/azure-py-msi-keyvault-rbac/webapp/Startup.cs b/azure-py-msi-keyvault-rbac/webapp/Startup.cs new file mode 100644 index 000000000..6975b7e2c --- /dev/null +++ b/azure-py-msi-keyvault-rbac/webapp/Startup.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace webapp +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + this.reader = new Reader(configuration); + } + + private Reader reader; + + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.Run(async (context) => + { + var userName = await this.reader.GetSqlUser(); + var data = await this.reader.GetBlobText(); + await context.Response.WriteAsync($"Hello {userName}!
Here is your README:
{data}"); + }); + } + } +} diff --git a/azure-py-msi-keyvault-rbac/webapp/webapp.csproj b/azure-py-msi-keyvault-rbac/webapp/webapp.csproj new file mode 100644 index 000000000..66c323758 --- /dev/null +++ b/azure-py-msi-keyvault-rbac/webapp/webapp.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp2.2 + + + + + + + + + + + diff --git a/azure-py-vm-scaleset/.gitignore b/azure-py-vm-scaleset/.gitignore new file mode 100644 index 000000000..0d20b6487 --- /dev/null +++ b/azure-py-vm-scaleset/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/azure-py-vm-scaleset/Pulumi.yaml b/azure-py-vm-scaleset/Pulumi.yaml new file mode 100644 index 000000000..f077831d8 --- /dev/null +++ b/azure-py-vm-scaleset/Pulumi.yaml @@ -0,0 +1,8 @@ +name: azure-py-vm-scaleset +runtime: python +description: Basic example of nginx deployed in Azure VM Scale Sets written in Python +template: + config: + azure:environment: + description: The Azure environment to use (`public`, `usgovernment`, `german`, `china`) + default: public diff --git a/azure-py-vm-scaleset/README.md b/azure-py-vm-scaleset/README.md new file mode 100644 index 000000000..c868763db --- /dev/null +++ b/azure-py-vm-scaleset/README.md @@ -0,0 +1,74 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Azure VM Scale Sets + +This example provisions a Scale Set of Linux web servers with nginx deployed, configured the auto-scaling based on CPU load, puts a Load Balancer in front of them, and gives it a public IP address. + +## Prerequisites + +- [Node.js](https://nodejs.org/en/download/) +- [Download and install the Pulumi CLI](https://www.pulumi.com/docs/get-started/install/) +- [Connect Pulumi with your Azure account](https://www.pulumi.com/docs/intro/cloud-providers/azure/setup/) (if your `az` CLI is + configured, this will just work) + +## Running the App + +1. Create a new stack: + + ``` + $ pulumi stack init dev + ``` + +1. Configure the app deployment. + + ``` + $ pulumi config set azure:location westus # any valid Azure region will do + ``` + + Optionally, configure the username and password for the admin user. Otherwise, they will be auto-generated. + + ``` + $ pulumi config set adminUser webmaster + $ pulumi config set adminPassword --secret + ``` + + Note that `--secret` ensures your password is encrypted safely. + +1. Login to Azure CLI (you will be prompted to do this during deployment if you forget this step): + + ``` + $ az login + ``` + +1. Create a Python virtualenv, activate it, and install dependencies: + + This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + + ``` + $ virtualenv -p python3 venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Run `pulumi up` to preview and deploy changes: + + ``` + $ pulumi up + Previewing update: + ... + + Updating: + ... + Resources: + 13 created + Update duration: 2m19s + ``` + +1. Check the domain name of the PIP: + + ``` + $ pulumi stack output publicAddress + dsuv3vqbgi.westeurope.cloudapp.azure.com + $ curl http://$(pulumi stack output publicAddress) + #nginx welcome screen HTML is returned + ``` diff --git a/azure-py-vm-scaleset/__main__.py b/azure-py-vm-scaleset/__main__.py new file mode 100644 index 000000000..ddc7ec72c --- /dev/null +++ b/azure-py-vm-scaleset/__main__.py @@ -0,0 +1,121 @@ +from pulumi import Config, export, ResourceOptions +from pulumi_azure import core, network, lb, compute +import pulumi_random as random + +config = Config() +admin_user = config.get("adminUser") or "azureuser" +admin_password = config.get_secret("adminPassword") or random.RandomPassword( + "pwd", + length=20, + special="true").result +domain = config.get("domain") or random.RandomString( + "domain", + length=10, + number="false", + special="false", + upper="false").result +application_port = config.get_float("applicationPort") or 80; + +resource_group = core.ResourceGroup("vmss-rg") + +public_ip = network.PublicIp( + "public-ip", + resource_group_name=resource_group.name, + allocation_method="Static", + domain_name_label=domain) + +load_balancer = lb.LoadBalancer( + "lb", + resource_group_name=resource_group.name, + frontend_ip_configurations=[{ + "name": "PublicIPAddress", + "publicIpAddressId": public_ip.id, + }]) + +bpepool = lb.BackendAddressPool( + "bpepool", + resource_group_name=resource_group.name, + loadbalancer_id=load_balancer.id) + +ssh_probe = lb.Probe( + "ssh-probe", + resource_group_name=resource_group.name, + loadbalancer_id=load_balancer.id, + port=application_port) + +nat_ule = lb.Rule( + "lbnatrule-http", + resource_group_name=resource_group.name, + backend_address_pool_id=bpepool.id, + backend_port=application_port, + frontend_ip_configuration_name="PublicIPAddress", + frontend_port=application_port, + loadbalancer_id=load_balancer.id, + probe_id=ssh_probe.id, + protocol="Tcp") + +vnet = network.VirtualNetwork( + "vnet", + resource_group_name=resource_group.name, + address_spaces=["10.0.0.0/16"]) + +subnet = network.Subnet( + "subnet", + resource_group_name=resource_group.name, + address_prefix="10.0.2.0/24", + virtual_network_name=vnet.name) + +scale_set = compute.ScaleSet( + "vmscaleset", + resource_group_name=resource_group.name, + network_profiles=[{ + "ipConfigurations": [{ + "load_balancer_backend_address_pool_ids": [bpepool.id], + "name": "IPConfiguration", + "primary": "true", + "subnet_id": subnet.id, + }], + "name": "networkprofile", + "primary": "true", + }], + os_profile={ + "admin_username": admin_user, + "admin_password": admin_password, + "computer_name_prefix": "vmlab", + "custom_data": """ + #cloud-config + packages: + - nginx + """ + }, + os_profile_linux_config={ + "disable_password_authentication": "false" + }, + sku={ + "capacity": 1, + "name": "Standard_DS1_v2", + "tier": "Standard" + }, + storage_profile_data_disks=[{ + "caching": "ReadWrite", + "create_option": "Empty", + "disk_size_gb": 10, + "lun": 0 + }], + storage_profile_image_reference={ + "offer": "UbuntuServer", + "publisher": "Canonical", + "sku": "16.04-LTS", + "version": "latest" + }, + storage_profile_os_disk={ + "caching": "ReadWrite", + "create_option": "FromImage", + "managed_disk_type": "Standard_LRS", + "name": "" + }, + upgrade_policy_mode="Manual", + __opts__=ResourceOptions(depends_on=[bpepool])) + +export("public_address", public_ip.fqdn) + diff --git a/azure-py-vm-scaleset/requirements.txt b/azure-py-vm-scaleset/requirements.txt new file mode 100644 index 000000000..53926ea42 --- /dev/null +++ b/azure-py-vm-scaleset/requirements.txt @@ -0,0 +1,3 @@ +pulumi>=1.0.0 +pulumi-azure>=1.0.0 +pulumi-random>=1.0.0 diff --git a/azure-ts-hdinsight-spark/index.ts b/azure-ts-hdinsight-spark/index.ts index 692d0ddbe..364bcfd1d 100644 --- a/azure-ts-hdinsight-spark/index.ts +++ b/azure-ts-hdinsight-spark/index.ts @@ -8,9 +8,7 @@ const username = config.require("username"); const password = config.require("password"); // Create an Azure Resource Group -const resourceGroup = new azure.core.ResourceGroup("spark-rg", { - location: azure.Locations.WestUS, -}); +const resourceGroup = new azure.core.ResourceGroup("spark-rg"); // Create a storage account and a container for Spark const storageAccount = new azure.storage.Account("sparksa", { @@ -44,18 +42,18 @@ const sparkCluster = new azure.hdinsight.SparkCluster("myspark", { }, roles: { headNode: { - vmSize: "Standard_A3", + vmSize: "Standard_D12_v2", username, password, }, workerNode: { targetInstanceCount: 3, - vmSize: "Standard_A3", + vmSize: "Standard_D12_v2", username, password, }, zookeeperNode: { - vmSize: "Standard_A3", + vmSize: "Standard_D12_v2", username, password, }, diff --git a/digitalocean-py-k8s/.gitignore b/digitalocean-py-k8s/.gitignore new file mode 100644 index 000000000..a3807e5bd --- /dev/null +++ b/digitalocean-py-k8s/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/digitalocean-py-k8s/Pulumi.yaml b/digitalocean-py-k8s/Pulumi.yaml new file mode 100644 index 000000000..f9bbcb494 --- /dev/null +++ b/digitalocean-py-k8s/Pulumi.yaml @@ -0,0 +1,3 @@ +name: digitalocean-py-k8s +runtime: python +description: Provision a DigitalOcean Kubernetes cluster and deploy to it in Python diff --git a/digitalocean-py-k8s/README.md b/digitalocean-py-k8s/README.md new file mode 100644 index 000000000..61632f447 --- /dev/null +++ b/digitalocean-py-k8s/README.md @@ -0,0 +1,118 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# DigitalOcean Kubernetes Cluster and Application + +This example provisions a new DigitalOcean Kubernetes cluster, deploys a load-balanced application into it, and then optionally configures DigitalOcean DNS records to give the resulting application a stable domain-based URL. This is demonstrated in Python. + +## Deploying the Example + +### Prerequisites + +To follow this example, you will need: + +1. [Install Pulumi](https://www.pulumi.com/docs/get-started/install/) +1. [Register for a DigitalOcean Account](https://cloud.digitalocean.com/registrations/new) +1. [Generate a DigitalOcean personal access token](https://www.digitalocean.com/docs/api/create-personal-access-token/) +1. [Install `kubectl` for accessing your cluster](https://kubernetes.io/docs/tasks/tools/install-kubectl/) + +If you want to configure the optional DigitalOcean DNS records at the end, you will also need: + +1. Obtain a domain name and [configure it to use DigitalOcean nameservers](https://www.digitalocean.com/community/tutorials/how-to-point-to-digitalocean-nameservers-from-common-domain-registrars) + +### Steps + +After cloning this repo, from this working directory, run these commands:``` + +1. Create a new Pulumi stack, which is an isolated deployment target for this example: + + ```bash + $ pulumi stack init dev + ``` + +1. Create a Python virtualenv, activate it, and install dependencies: + + This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + + ``` + $ virtualenv -p python3 venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Configure Pulumi to use your DigitalOcean personal access token: + + ```bash + $ pulumi config set digitalocean:token --secret + ``` + +1. (Optional) If you wish to use a custom domain name, configure it now: + + ```bash + $ pulumi config set domainName + ``` + +1. Deploy your cluster, application, and optional DNS records by running `pulumi up`. + + This command shows a preview of the resources that will be created and asks you + whether to proceed with the deployment. Select "yes" to perform the deployment. + + ```bash + $ pulumi up + Updating (dev): + + Type Name Status + + pulumi:pulumi:Stack do-k8s-dev created + + └─ digitalocean:index:KubernetesCluster do-cluster created + + ├─ pulumi:providers:kubernetes do-k8s created + + ├─ kubernetes:apps:Deployment do-app-dep created + + └─ kubernetes:core:Service do-app-svc created + + ├─ digitalocean:index:Domain do-domain created + + └─ digitalocean:index:DnsRecord do-domain-cname created + + Outputs: + + ingressIp : "157.230.199.202" + + Resources: + + 7 created + + Duration: 6m5s + ``` + + Note that the entire deployment will typically take between 4-8 minutes. + + As part of the update, you'll see some new objects in the output, including + a `Deployment` resource for the NGINX app, and a LoadBalancer `Service` to + publicly access NGINX, for example. + +1. After 3-5 minutes, your cluster will be ready, and the kubeconfig JSON you'll + use to connect to the cluster will be available as an output. + + To access your cluster, save your `kubeconfig` stack output to a file and then + use that when running the `kubectl` command. For instance, this lists your pods: + + ```bash + $ pulumi stack output kubeconfig > kubeconfig + $ KUBECONFIG=./kubeconfig kubectl get pods + ``` + +1. Pulumi understands which changes to a given cloud resource can be made in-place, + and which require replacement, and computes the minimally disruptive change to + achieve the desired state. Let's make a small change: + + ```bash + $ pulumi config set appReplicaCount 7 + ``` + + And then rerun `pulumi up`. Notice that it shows the preview of the changes, + including a diff of the values changed. Select "yes" to perform the update. + +1. From here, feel free to experiment a little bit. Once you've finished experimenting, + tear down your stack's resources by destroying and removing it: + + ```bash + $ pulumi destroy --yes + $ pulumi stack rm --yes + ``` + + This not only removes the underlying DigitalOcean cloud resources, but also + deletes the stack and its history from Pulumi also. diff --git a/digitalocean-py-k8s/__main__.py b/digitalocean-py-k8s/__main__.py new file mode 100644 index 000000000..fc840dc5f --- /dev/null +++ b/digitalocean-py-k8s/__main__.py @@ -0,0 +1,59 @@ +import pulumi_digitalocean as do +from pulumi import Config, export, Output, ResourceOptions +from pulumi_kubernetes import Provider +from pulumi_kubernetes.apps.v1 import Deployment +from pulumi_kubernetes.core.v1 import Service + +config = Config() +node_count = config.get_float("nodeCount") or 3 +app_replica_count = config.get_float("appReplicaCount") or 5 +domain_name = config.get("domainName") + +cluster = do.KubernetesCluster( + "do-cluster", + region="sfo2", + version="1.15.3-do.2", + node_pool={ + "name": "default", + "size": "s-2vcpu-2gb", + "node_count": node_count + }) + +k8s_provider = Provider("do-k8s", kubeconfig=cluster.kube_configs[0]["rawConfig"] ) + +app_labels = { "app": "app-nginx" } +app = Deployment( + "do-app-dep", + spec={ + 'selector': { 'matchLabels': app_labels }, + 'replicas': 1, + 'template': { + 'metadata': { 'labels': app_labels }, + 'spec': { 'containers': [{ 'name': 'nginx', 'image': 'nginx' }] }, + }, + }, __opts__=ResourceOptions(provider=k8s_provider)) + +ingress = Service( + 'do-app-svc', + spec={ + 'type': 'LoadBalancer', + 'selector': app_labels, + 'ports': [{'port': 80}], + }, __opts__=ResourceOptions(provider=k8s_provider, custom_timeouts={"create":"15m", "delete": "15m"})) + +ingress_ip=ingress.status['load_balancer']['ingress'][0]['ip'] + +export('ingress_ip', ingress_ip) + +if domain_name: + domain = do.Domain( + "do-domain", + name=domain_name, + ip_address=ingress_ip) + + cname_record = do.DnsRecord( + "do-domain-name", + domain=domain_name, + type="CNAME", + name="www", + value="@") diff --git a/digitalocean-py-k8s/requirements.txt b/digitalocean-py-k8s/requirements.txt new file mode 100644 index 000000000..e414ab0fd --- /dev/null +++ b/digitalocean-py-k8s/requirements.txt @@ -0,0 +1,3 @@ +pulumi>=1.0.0 +pulumi-kubernetes>=1.0.0 +pulumi-digitalocean>=0.18.13 diff --git a/digitalocean-py-loadbalanced-droplets/.gitignore b/digitalocean-py-loadbalanced-droplets/.gitignore new file mode 100644 index 000000000..a3807e5bd --- /dev/null +++ b/digitalocean-py-loadbalanced-droplets/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/digitalocean-py-loadbalanced-droplets/Pulumi.yaml b/digitalocean-py-loadbalanced-droplets/Pulumi.yaml new file mode 100644 index 000000000..50b320173 --- /dev/null +++ b/digitalocean-py-loadbalanced-droplets/Pulumi.yaml @@ -0,0 +1,3 @@ +name: digitalocean-py-loadbalanced-droplets +runtime: python +description: Basic example of load balanced droplets on DigitalOcean in Python diff --git a/digitalocean-py-loadbalanced-droplets/README.md b/digitalocean-py-loadbalanced-droplets/README.md new file mode 100644 index 000000000..914d7f74b --- /dev/null +++ b/digitalocean-py-loadbalanced-droplets/README.md @@ -0,0 +1,71 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Pulumi DigitalOcean Droplets + +Starting point for building a Pulumi sample architecture on DigitalOcean. + +## Running the App + +1. Create a new stack: + + ``` + $ pulumi stack init digitalocean-ts-loadbalanced-droplets + ``` + +1. Configure the project: + + ``` + $ pulumi config set --secret digitalocean:token YOURDIGITALOCEANTOKEN + ``` + +1. Create a Python virtualenv, activate it, and install dependencies: + + This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + + ``` + $ virtualenv -p python3 venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Run `pulumi up` to preview and deploy changes: + + ``` + $ pulumi up + Previewing update (digitalocean-ts-loadbalanced-droplets): + ... + +Updating (digitalocean-ts-loadbalanced-droplets): + + Type Name Status + + pulumi:pulumi:Stack digitalocean-ts-loadbalanced-droplets-digitalocean-ts-loadbalanced-droplets created + + ├─ digitalocean:index:Tag demo-app created + + ├─ digitalocean:index:Tag web-2 created + + ├─ digitalocean:index:Tag web-0 created + + ├─ digitalocean:index:Tag web-1 created + + ├─ digitalocean:index:LoadBalancer public created + + ├─ digitalocean:index:Droplet web-0 created + + ├─ digitalocean:index:Droplet web-2 created + + └─ digitalocean:index:Droplet web-1 created + +Outputs: + endpoint: "138.197.62.183" + +Resources: + + 9 created + +Duration: 3m2s + ``` + +1. Curl the HTTP server: + + ``` + curl "$(pulumi stack output endpoint)" + ``` + +1. Cleanup + + ``` + $ pulumi destroy + $ pulumi stack rm + ``` diff --git a/digitalocean-py-loadbalanced-droplets/__main__.py b/digitalocean-py-loadbalanced-droplets/__main__.py new file mode 100644 index 000000000..99754e3d2 --- /dev/null +++ b/digitalocean-py-loadbalanced-droplets/__main__.py @@ -0,0 +1,43 @@ +import pulumi_digitalocean as do +from pulumi import export + +droplet_count = 3 +region = "nyc3" + +user_data = """#!/bin/bash + sudo apt-get update + sudo apt-get install -y nginx +""" + +droplet_type_tag = do.Tag("demo-app") + + +for x in range(0, droplet_count): + instance_name = "web-%s" %x + name_tag = do.Tag(instance_name) + droplet = do.Droplet( + instance_name, + image="ubuntu-18-04-x64", + region=region, + size="512mb", + tags=[name_tag.id, droplet_type_tag.id], + user_data=user_data + ) + +loadbalancer = do.LoadBalancer( + "public", + droplet_tag=droplet_type_tag.name, + forwarding_rules=[{ + "entry_port": 80, + "entry_protocol": "http", + "target_port": 80, + "target_protocol": "http", + }], + healthcheck={ + "port": 80, + "protocol": "tcp", + }, + region=region, +) + +export("endpoint", loadbalancer.ip) diff --git a/digitalocean-py-loadbalanced-droplets/requirements.txt b/digitalocean-py-loadbalanced-droplets/requirements.txt new file mode 100644 index 000000000..aa4558856 --- /dev/null +++ b/digitalocean-py-loadbalanced-droplets/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=1.0.0 +pulumi-digitalocean>=0.18.13 diff --git a/gcp-py-serverless-raw/.gitignore b/gcp-py-serverless-raw/.gitignore new file mode 100644 index 000000000..a3807e5bd --- /dev/null +++ b/gcp-py-serverless-raw/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/gcp-py-serverless-raw/Pulumi.yaml b/gcp-py-serverless-raw/Pulumi.yaml new file mode 100644 index 000000000..12ee82872 --- /dev/null +++ b/gcp-py-serverless-raw/Pulumi.yaml @@ -0,0 +1,9 @@ +name: serverless-py +runtime: python +description: Basic example of a serverless Python and Go GCP functions (in Python) +template: + config: + gcp:project: + description: The Google Cloud project to deploy into + gcp:region: + description: The Google Cloud region diff --git a/gcp-py-serverless-raw/README.md b/gcp-py-serverless-raw/README.md new file mode 100644 index 000000000..4ae4d4b9b --- /dev/null +++ b/gcp-py-serverless-raw/README.md @@ -0,0 +1,37 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Google Cloud Functions in Python and Go deployed + +This example deploys two Google Cloud Functions. "Hello World" functions are implemented in Python and Go. Pulumi program is implemented in Python. + +```bash +# Create and configure a new stack +$ pulumi stack init testing +$ pulumi config set gcp:project +$ pulumi config set gcp:region + +# This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + +$ virtualenv -p python3 venv +$ source venv/bin/activate +$ pip3 install -r requirements.txt + +# Preview and run the deployment +$ pulumi up +Previewing changes: +... +Performing changes: +... +info: 6 changes performed: + + 6 resources created +Update duration: 1m14s + +# Test it out +$ curl $(pulumi stack output python_endpoint) +"Hello World!" +$ curl $(pulumi stack output go_endpoint) +"Hello World!" + +# Remove the app +$ pulumi destroy +``` diff --git a/gcp-py-serverless-raw/__main__.py b/gcp-py-serverless-raw/__main__.py new file mode 100644 index 000000000..a061d16c0 --- /dev/null +++ b/gcp-py-serverless-raw/__main__.py @@ -0,0 +1,42 @@ +from pulumi_gcp import storage, cloudfunctions +from pulumi import export, asset + +bucket = storage.Bucket("bucket") + +py_bucket_object = storage.BucketObject( + "python-zip", + bucket=bucket.name, + source=asset.AssetArchive({ + ".": asset.FileArchive("./pythonfunc") + })) + +py_function = cloudfunctions.Function( + "python-func", + source_archive_bucket=bucket.name, + runtime="python37", + source_archive_object=py_bucket_object.name, + entry_point="handler", + trigger_http="true", + available_memory_mb=128, +) + +export("python_endpoint", py_function.https_trigger_url) + +go_bucket_object = storage.BucketObject( + "go-zip", + bucket=bucket.name, + source=asset.AssetArchive({ + ".": asset.FileArchive("./gofunc") + })) + +go_function = cloudfunctions.Function( + "go-func", + source_archive_bucket=bucket.name, + runtime="go111", + source_archive_object=go_bucket_object.name, + entry_point="Handler", + trigger_http="true", + available_memory_mb=128, +) + +export("go_endpoint", go_function.https_trigger_url) diff --git a/gcp-py-serverless-raw/gofunc/function.go b/gcp-py-serverless-raw/gofunc/function.go new file mode 100644 index 000000000..8f55f24cc --- /dev/null +++ b/gcp-py-serverless-raw/gofunc/function.go @@ -0,0 +1,12 @@ +package helloworld + +import ( + "fmt" + "net/http" +) + +func Handler(w http.ResponseWriter, r *http.Request) { + + w.Header().Set("Content-Type", "text/plain") + fmt.Fprintf(w, "Hello World!") +} diff --git a/gcp-py-serverless-raw/pythonfunc/main.py b/gcp-py-serverless-raw/pythonfunc/main.py new file mode 100644 index 000000000..5856dd36a --- /dev/null +++ b/gcp-py-serverless-raw/pythonfunc/main.py @@ -0,0 +1,6 @@ +def handler(request): + headers = { + 'Content-Type': 'text/plain' + } + + return ('Hello World!', 200, headers) diff --git a/gcp-py-serverless-raw/requirements.txt b/gcp-py-serverless-raw/requirements.txt new file mode 100644 index 000000000..a6f64568e --- /dev/null +++ b/gcp-py-serverless-raw/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=1.0.0 +pulumi-gcp>=1.0.0 diff --git a/misc/test/examples_test.go b/misc/test/examples_test.go index 4df9f7d17..229db769c 100644 --- a/misc/test/examples_test.go +++ b/misc/test/examples_test.go @@ -116,6 +116,18 @@ func TestExamples(t *testing.T) { assertHTTPHelloWorld(t, stack.Outputs["webUrl"], nil) }, }), + base.With(integration.ProgramTestOptions{ + Dir: path.Join(cwd, "..", "..", "aws-py-appsync"), + Config: map[string]string{ + "aws:region": awsRegion, + }, + }), + base.With(integration.ProgramTestOptions{ + Dir: path.Join(cwd, "..", "..", "aws-py-resources"), + Config: map[string]string{ + "aws:region": awsRegion, + }, + }), base.With(integration.ProgramTestOptions{ Dir: path.Join(cwd, "..", "..", "aws-py-s3-folder"), Config: map[string]string{ @@ -352,11 +364,53 @@ func TestExamples(t *testing.T) { Config: map[string]string{ "azure:environment": azureEnviron, "password": "testTEST1234+_^$", - "prefix": "acctest", "sshkey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDeREOgHTUgPT00PTr7iQF9JwZQ4QF1VeaLk2nHKRvWYOCiky6hDtzhmLM0k0Ib9Y7cwFbhObR+8yZpCgfSX3Hc3w2I1n6lXFpMfzr+wdbpx97N4fc1EHGUr9qT3UM1COqN6e/BEosQcMVaXSCpjqL1jeNaRDAnAS2Y3q1MFeXAvj9rwq8EHTqqAc1hW9Lq4SjSiA98STil5dGw6DWRhNtf6zs4UBy8UipKsmuXtclR0gKnoEP83ahMJOpCIjuknPZhb+HsiNjFWf+Os9U6kaS5vGrbXC8nggrVE57ow88pLCBL+3mBk1vBg6bJuLBCp2WTqRzDMhSDQ3AcWqkucGqf dremy@remthinkpad", }, }), - + base.With(integration.ProgramTestOptions{ + Dir: path.Join(cwd, "..", "..", "azure-py-appservice"), + Config: map[string]string{ + "azure:environment": azureEnviron, + "azure:location": azureLocation, + "sqlPassword": "2@Password@2", + }, + ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { + assertHTTPResult(t, stack.Outputs["endpoint"], nil, func(body string) bool { + return assert.Contains(t, body, "Greetings from Azure App Service!") + }) + }, + }), + base.With(integration.ProgramTestOptions{ + Dir: path.Join(cwd, "..", "..", "azure-py-appservice-docker"), + Config: map[string]string{ + "azure:environment": azureEnviron, + "azure:location": azureLocation, + }, + ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { + assertHTTPResult(t, stack.Outputs["hello_endpoint"], nil, func(body string) bool { + return assert.Contains(t, body, "Hello, world!") + }) + }, + }), + base.With(integration.ProgramTestOptions{ + Dir: path.Join(cwd, "..", "..", "azure-py-hdinsight-spark"), + Config: map[string]string{ + "azure:location": azureLocation, + "username": "testuser", + "password": "MyPassword123+-*/", + }, + }), + base.With(integration.ProgramTestOptions{ + Dir: path.Join(cwd, "..", "..", "azure-py-vm-scaleset"), + Config: map[string]string{ + "azure:location": azureLocation, + }, + ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { + assertHTTPResult(t, stack.Outputs["public_address"].(string), nil, func(body string) bool { + return assert.Contains(t, body, "nginx") + }) + }, + }), base.With(integration.ProgramTestOptions{ Dir: path.Join(cwd, "..", "..", "azure-py-webserver"), Config: map[string]string{ @@ -437,14 +491,14 @@ func TestExamples(t *testing.T) { // azure-ts-functions-raw require specific language setups for tests - //base.With(integration.ProgramTestOptions{ - // Dir: path.Join(cwd, "..", "..", "azure-ts-hdinsight-spark"), - // Config: map[string]string{ - // "azure:location": azureLocation, - // "username": "testuser", - // "password": "MyPassword123+-*/", - // }, - //}), + base.With(integration.ProgramTestOptions{ + Dir: path.Join(cwd, "..", "..", "azure-ts-hdinsight-spark"), + Config: map[string]string{ + "azure:location": azureLocation, + "username": "testuser", + "password": "MyPassword123+-*/", + }, + }), // azure-ts-msi-keyvault-rbac requires DotNet setup @@ -620,6 +674,36 @@ func TestExamples(t *testing.T) { }, }), + base.With(integration.ProgramTestOptions{ + Dir: path.Join(cwd, "..", "..", "digitalocean-py-k8s"), + Config: map[string]string{}, + ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { + assertHTTPResult(t, stack.Outputs["ingress_ip"].(string), nil, func(body string) bool { + return assert.Contains(t, body, "Welcome to nginx!") + }) + }, + }), + + base.With(integration.ProgramTestOptions{ + Dir: path.Join(cwd, "..", "..", "digitalocean-py-loadbalanced-droplets"), + Config: map[string]string{}, + ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { + assertHTTPResult(t, stack.Outputs["endpoint"].(string), nil, func(body string) bool { + return assert.Contains(t, body, "Welcome to nginx!") + }) + }, + }), + + base.With(integration.ProgramTestOptions{ + Dir: path.Join(cwd, "..", "..", "digitalocean-ts-k8s"), + Config: map[string]string{}, + ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { + assertHTTPResult(t, stack.Outputs["ingressIp"].(string), nil, func(body string) bool { + return assert.Contains(t, body, "Welcome to nginx!") + }) + }, + }), + base.With(integration.ProgramTestOptions{ Dir: path.Join(cwd, "..", "..", "digitalocean-ts-loadbalanced-droplets"), Config: map[string]string{}, @@ -671,19 +755,36 @@ func TestExamples(t *testing.T) { "master_version": gkeEngineVersion, }, }), - //base.With(integration.ProgramTestOptions{ - // Dir: path.Join(cwd, "..", "..", "gcp-py-instance-nginx"), - // Config: map[string]string{ - // "gcp:project": "pulumi-ci-gcp-provider", - // "gcp:zone": "us-central1-a", - // }, - // ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { - // endpoint := stack.Outputs["external_ip"].(string) - // assertHTTPResult(t, endpoint, nil, func(body string) bool { - // return assert.Contains(t, body, "Test Page for the Nginx HTTP Server on Fedora") - // }) - // }, - //}), + base.With(integration.ProgramTestOptions{ + Dir: path.Join(cwd, "..", "..", "gcp-py-serverless-raw"), + Config: map[string]string{ + "gcp:project": "pulumi-ci-gcp-provider", + "gcp:zone": "us-central1-a", + }, + ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { + endpoint := stack.Outputs["go_endpoint"].(string) + assertHTTPResult(t, endpoint, nil, func(body string) bool { + return assert.Contains(t, body, "Hello World!") + }) + assertHTTPResult(t, stack.Outputs["python_endpoint"].(string), nil, func(body string) bool { + return assert.Contains(t, body, "Hello World!") + }) + }, + }), + base.With(integration.ProgramTestOptions{ + Dir: path.Join(cwd, "..", "..", "gcp-py-instance-nginx"), + Config: map[string]string{ + "gcp:project": "pulumi-ci-gcp-provider", + "gcp:zone": "us-central1-a", + }, + ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { + endpoint := stack.Outputs["external_ip"].(string) + maxWait := time.Minute * 5 + assertHTTPResultWithRetry(t, endpoint, nil, maxWait, func(body string) bool { + return assert.Contains(t, body, "Test Page for the Nginx HTTP Server on Fedora") + }) + }, + }), base.With(integration.ProgramTestOptions{ Dir: path.Join(cwd, "..", "..", "gcp-ts-functions"), Config: map[string]string{ @@ -734,6 +835,9 @@ func TestExamples(t *testing.T) { assertHTTPResult(t, endpoint, nil, func(body string) bool { return assert.Contains(t, body, "Hello World!") }) + assertHTTPResult(t, stack.Outputs["pythonEndpoint"].(string), nil, func(body string) bool { + return assert.Contains(t, body, "Hello World!") + }) }, }), //base.With(integration.ProgramTestOptions{