{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Pulumi Automation API\n", "\n", "Pulumi's Automation API is the programmatic interface for driving pulumi programs from within your code.\n", "The package can be used for a number of use cases:\n", "\n", " * Driving pulumi deployments within CI/CD workflows\n", " * Integration testing\n", " * Multi-stage deployments such as blue-green deployment patterns\n", " * Deployments involving application code like database migrations\n", " * Building higher level tools, custom CLIs over pulumi, etc\n", " * Using pulumi behind a REST or GRPC API\n", " * Debugging Pulumi programs (by using a single main entrypoint with \"inline\" programs)\n", "\n", "This jupyter notebook explores various facets of automation API itself and explores how to deploy infrastructure without ever leaving the notebook.\n", "\n", "To run this example you'll need a few pre-reqs:\n", "\n", " 1. A Pulumi CLI installation ([v3.0.0](https://www.pulumi.com/docs/get-started/install/versions/) or later)\n", " 2. The AWS CLI, with appropriate credentials.\n", "\n", "Alright, let's get started.\n", "\n", "### Automation API 101\n", "\n", "In addition to fine-grained building blocks, Automation API provides two out-of-the-box ways to work with Stacks:\n", "\n", "1. Programs locally available on-disk and addressed via a filepath (local source):\n", "\n", " ```python\n", " stack = create_stack(\"myOrg/myProj/myStack\", work_dir=os.path.join(\"..\", \"path\", \"to\", \"project\"))\n", " ```\n", "\n", "2. Programs defined as a function alongside your Automation API code (inline source):\n", "\n", " ```python\n", " def pulumi_program():\n", " bucket = s3.Bucket(\"bucket\")\n", " pulumi.export(\"bucket_name\", bucket.Bucket)\n", "\n", " stack = create_stack(\"myOrg/myProj/myStack\", program=pulumi_program)\n", " ```\n", "\n", "Each of these creates a stack with access to the full range of Pulumi lifecycle methods\n", "(up/preview/refresh/destroy), as well as methods for managing config, stack, and project settings:\n", "\n", "```python\n", "stack.set_config(\"key\", ConfigValue(value=\"value\", secret=True))\n", "preview_response = stack.preview()\n", "```\n", "\n", "\n", "### Pulumi programs as functions\n", "\n", "An inline program allows you to define your infrastructure within a function alongside your other code. Consider the following function called `s3_static_site`. It creates an s3 bucket, sets it up as a basic static website and exports the URL.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pulumi\n", "from pulumi_aws import s3\n", "\n", "def s3_static_site():\n", " # Create a bucket and expose a website index document\n", " site_bucket = s3.Bucket(\"s3-website-bucket\", website=s3.BucketWebsiteArgs(index_document=\"index.html\"))\n", " index_content = \"\"\"\n", " \n", " Hello S3\n", " \n", "

Hello, world!

\n", "

Made with ❤️ with Pulumi

\n", " \n", " \n", " \"\"\"\n", "\n", " # Write our index.html into the site bucket\n", " s3.BucketObject(\"index\",\n", " bucket=site_bucket.id, # reference to the s3.Bucket object\n", " content=index_content,\n", " key=\"index.html\", # set the key of the object\n", " content_type=\"text/html; charset=utf-8\") # set the MIME type of the file\n", "\n", " # Set the access policy for the bucket so all objects are readable\n", " s3.BucketPolicy(\"bucket-policy\", bucket=site_bucket.id, policy={\n", " \"Version\": \"2012-10-17\",\n", " \"Statement\": {\n", " \"Effect\": \"Allow\",\n", " \"Principal\": \"*\",\n", " \"Action\": [\"s3:GetObject\"],\n", " # Policy refers to bucket explicitly\n", " \"Resource\": [pulumi.Output.concat(\"arn:aws:s3:::\", site_bucket.id, \"/*\")]\n", " },\n", " })\n", "\n", " # Export the website URL\n", " pulumi.export(\"website_url\", site_bucket.website_endpoint)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Automating your deployment\n", "\n", "Now, let's define some functions to deploy and destroy our stacks." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from typing import List, Tuple, Optional, Dict\n", "from pulumi import automation as auto\n", "\n", "stack_name = \"dev\"\n", "\n", "def noop():\n", " pass\n", "\n", "def deploy_project(project_name: str,\n", " program: callable,\n", " plugins: Optional[List[Tuple]] = None,\n", " config: Optional[Dict[str, auto.ConfigValue]] = None):\n", " # create (or select if one already exists) a stack that uses our inline program\n", " stack = auto.create_or_select_stack(stack_name=stack_name,\n", " project_name=project_name,\n", " program=program)\n", "\n", " if plugins:\n", " for plugin in plugins:\n", " stack.workspace.install_plugin(plugin[0], plugin[1])\n", " print(\"plugins installed\")\n", "\n", " if config:\n", " stack.set_all_config(config)\n", " print(\"config set\")\n", "\n", " stack.refresh(on_output=print)\n", "\n", " stack.up(on_output=print)\n", "\n", " return stack\n", "\n", "def destroy_project(project_name: str):\n", " stack = auto.create_or_select_stack(stack_name=stack_name,\n", " project_name=project_name,\n", " program=noop)\n", "\n", " stack.destroy(on_output=print)\n", "\n", " stack.workspace.remove_stack(stack_name)\n", " print(f\"stack {stack_name} in project {project_name} removed\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Deploy all the things!\n", "\n", "Alright, we're ready to deploy our first project. Execute the code below and watch the output as your program progresses." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "s3_site = deploy_project(\"my_first_project\",\n", " s3_static_site,\n", " plugins=[(\"aws\", \"v4.0.0\")],\n", " config={\"aws:region\": auto.ConfigValue(value=\"us-west-2\")})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using stack outputs\n", "\n", "Now that our stack is deployed, let's make sure everything was deployed correctly by making a request to the URL we exported." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "outputs = s3_site.outputs()\n", "url = f\"http://{outputs['website_url'].value}\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import requests\n", "\n", "site_content = requests.get(url).text\n", "site_content" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Cool! Looks like we got some HTML back. Let's display it in our notebook using IPython." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import HTML\n", "\n", "HTML(site_content)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alright, that looks much better. We can even open our website in a new browser tab." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import webbrowser\n", "\n", "outputs = s3_site.outputs()\n", "\n", "webbrowser.open(url)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Clean up\n", "\n", "Now that we're done testing everything out, we can destroy our stack." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "destroy_project(\"my_first_project\")\n" ] } ], "metadata": { "kernelspec": { "display_name": "PyCharm (automation-api-examples)", "language": "python", "name": "pycharm-327b49b8" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.7" } }, "nbformat": 4, "nbformat_minor": 4 }