├── LICENSE ├── content ├── 01-what-is-cnab.md ├── 02-prerequisites.md ├── 03-build-sign.md ├── 04-params-creds.md ├── 05-export-import.md ├── 10-porter.md ├── 12-manifest-deps.md ├── 12-porter-manifest.md ├── 13-porter-mixins.md ├── 15-porter-installation.md ├── 16-exercise-1.md └── 16-exercise-2.md └── readme.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /content/01-what-is-cnab.md: -------------------------------------------------------------------------------- 1 | # What is [CNAB][cnab]? 2 | 3 | > A more detailed description for Cloud Native Application Bundles can be found in [the official CNAB specification, on GitHub][cnab-spec]. 4 | 5 | [Cloud Native Application Bundles][cnab] (CNAB) are a _standard packaging format_ for distributed applications. It allows packages to target different runtimes and architectures. It empowers application distributors to package applications for deployment on a wide variety of cloud platforms, cloud providers, and cloud services. It also provides the capabilities necessary for delivering applications in disconnected environments. 6 | 7 | CNAB is not a platform-specific tool. While it uses containers for encapsulating installation logic, it remains un-opinionated about what cloud environment it runs in. CNAB developers can bundle applications targeting environments spanning IaaS (like OpenStack or Azure), container orchestrators (like Kubernetes or Nomad), container runtimes (like local Docker or ACI), and cloud platform services (like object storage or Database as a Service). CNAB can also be used for packaging other distributed applications, such as IoT or edge computing. 8 | 9 | The CNAB format is a packaging format for a broad range of distributed applications. It specifies a pairing of a _bundle definition_ [(`bundle.json`)](https://github.com/deislabs/cnab-spec/blob/master/101-bundle-json.md) to define the app, and an _invocation image_ to install the app. 10 | The invocation image contains a standardized filesystem layout where metadata and installation data is stored in predictable places. A run tool is the executable entry point into a CNAB bundle. Parameterization and credentialing allow injection of configuration data into the invocation image. The invocation image is described in detail in the invocation image definition. 11 | 12 | 13 | The _bundle definition_ is a single file that contains the following information: 14 | 15 | - Information about the bundle, such as name, bundle version, description, and keywords 16 | - Information about locating and running the _invocation image_ (the installer program) 17 | - A list of user-overridable parameters that this package recognizes 18 | - The list of executable images that this bundle will install 19 | - A list of credential paths or environment variables that this bundle requires to execute 20 | 21 | The bundle definition can: 22 | 23 | - An unsigned JSON Object stored in a `bundle.json` file, as defined in [the bundle file definition](https://github.com/deislabs/cnab-spec/blob/master/101-bundle-json.md) 24 | - A signed JSON object stored in a `bundle.cnab` file, as described in [the signature definition](https://github.com/deislabs/cnab-spec/blob/master/105-signing.md) - as a signed bundle definition represents an immutable bundle, all invocation images and images references must have a digest. 25 | 26 | 27 | 28 | [cnab]: https://cnab.io 29 | [cnab-spec]: https://github.com/deislabs/cnab-spec/ 30 | [bundle-json]: https://github.com/deislabs/cnab-spec/blob/master/101-bundle-json.md 31 | -------------------------------------------------------------------------------- /content/02-prerequisites.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | Now that we're familiar with Cloud Native Application Bundles, it's time to set up our development environment and start using [Duffle][duffle], a reference implementation for CNAB: 4 | 5 | - download [the latest version of Duffle for your platform][duffle-releases] and make sure the executable is available in your path 6 | - install [Docker Community Edition for your platform][docker-ce] 7 | - a Kubernetes cluster (see how to create an [Azure Kubernetes Service (AKS) cluster][create-aks]) 8 | 9 | 10 | > The `DOCKER_HOST` environment variable can be used to point to any Docker Engine instance, so any accessible Docker endpoint can be used with Duffle. 11 | 12 | > [Instructions on how to activate an Azure Pass][azure-pass] in order to create an Azure Kubernetes Service. 13 | 14 | To check if Duffle is installed locally, try to check the version. Depending on the version you downloaded, the output will be similar to: 15 | 16 | ```console 17 | $ duffle version 18 | 0.1.0-ralpha.5+englishrose 19 | ``` 20 | 21 | If you already have a Go development environment configured, you can also clone the repository and build the binary yourself. Instructions for this can be found in [the readme file for the Duffle GitHub repository][duffle-readme] 22 | 23 | [duffle]: https://github.com/deislabs/duffle 24 | [duffle-releases]: https://github.com/deislabs/duffle/releases 25 | [docker-ce]: https://docs.docker.com/install/#supported-platforms 26 | [azure-pass]: https://https://www.microsoftazurepass.com/ 27 | [create-aks]: https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough 28 | [duffle-readme]: https://github.com/deislabs/duffle/blob/master/README.md 29 | -------------------------------------------------------------------------------- /content/03-build-sign.md: -------------------------------------------------------------------------------- 1 | # Building Cloud Native Application Bundles with Duffle 2 | 3 | > Duffle is the first reference implementation for CNAB - because it follows the specification, any tool that implements the CNAB specification and outputs a valid bundle can be used to build bundles. 4 | 5 | # Creating, building and installing bundles 6 | 7 | Now that we have an understanding of what is a bundle, and our development environment is correctly setup, it's time to build our first bundle: 8 | 9 | - first of all, we create a directory structure for our bundle: 10 | 11 | ```console 12 | $ duffle create helloworld 13 | ==> Generating a new secret keyring at /home/radu/.duffle/secret.ring 14 | ==> Generating a new signing key with ID 15 | ==> Generating a new public keyring at /home/radu/.duffle/public.ring 16 | Creating helloworld 17 | ``` 18 | 19 | This command initializes Duffle for the first time, then creates a new directory called `helloworld` (the only argument passed to the `duffle create` command). This directory contains the necessary directory structure to create our bundle: 20 | 21 | - a build manifest file - `duffle.json` - this contains all required information to build our bundle 22 | - a `cnab` directory, which contains a Dockerfile (used to build the invocation image) and an `app` directory, which in this case only contains a 23 | 24 | ``` 25 | $ cd helloworld && ls 26 | cnab duffle.json 27 | 28 | $ ls cnab/ 29 | Dockerfile app 30 | ``` 31 | 32 | > The specification describes [the bundle runtime][bundle-runtime] in detail - but in short, when executing an _action_ on a bundle (such as _install_, or _upgrade_), any tool that implements the CNAB specification must run the executable found in `/cnab/app/run` inside the invocation image. This needs the _action_ to be executed, and uses environment variables and files injected into the invocation image at runtime to execute the required action. 33 | 34 | 35 | In this case, we have the simplest executable that satisfies the requirements for the bundle runtime - a Bash script - but keep in mind that any executable that correctly satisfies the requirements for the bundle runtime can be used as the run tool. 36 | 37 | > Note that in a normal workflow for developing bundles, the author is not always supposed to write the run tool from scratch - there is already [an existing collection of bundles][bundles] for tools such as Terraform, Helm or Ansible, and when developing a bundle that uses these tools, these _base bundles_ can be used. 38 | 39 | ```bash 40 | $ cat cnab/app/run 41 | 42 | #!/bin/bash 43 | action=$CNAB_ACTION 44 | if [[ $action == "install" ]]; then 45 | echo "hey I am installing things over here" 46 | elif [[ $action == "uninstall" ]]; then 47 | echo "hey I am uninstalling things now" 48 | fi 49 | ``` 50 | 51 | At this point, we can execute the `duffle build` command, which will build the invocation image, together with the bundle (with information from the build manifest -`duffle.json`): 52 | 53 | ``` 54 | $ duffle build 55 | Step 1/5 : FROM alpine:latest 56 | ---> 196d12cf6ab1 57 | Step 2/5 : RUN apk add -u bash 58 | ---> Using cache 59 | ---> 8da8b360c44d 60 | Step 3/5 : COPY Dockerfile /cnab/Dockerfile 61 | ---> Using cache 62 | ---> bcc0092953b7 63 | Step 4/5 : COPY app /cnab/app 64 | ---> Using cache 65 | ---> fe379df2e706 66 | Step 5/5 : CMD ["/cnab/app/run"] 67 | ---> Using cache 68 | ---> 0563a8f09431 69 | Successfully built 0563a8f09431 70 | Successfully tagged deislabs/helloworld-cnab:37db1697dab0116c78b3cc862fa105ebb380094d 71 | ==> Successfully built bundle helloworld:0.1.0 72 | 73 | $ duffle bundle list 74 | NAME VERSION DIGEST SIGNED? 75 | helloworld 0.1.0 a35f1404b19f26890a4fc4f625cdef5d2cd953b5 true 76 | ``` 77 | 78 | We can now inspect the bundle manifest using the `duffle show` command: 79 | 80 | ``` 81 | $ duffle show helloworld 82 | { 83 | "name": "helloworld", 84 | "version": "0.1.0", 85 | "description": "A short description of your bundle", 86 | "keywords": [ 87 | "helloworld", 88 | "cnab", 89 | "tutorial" 90 | ], 91 | "maintainers": [ 92 | { 93 | "name": "John Doe", 94 | "email": "john.doe@example.com", 95 | "url": "https://example.com" 96 | }, 97 | { 98 | "name": "Jane Doe", 99 | "email": "jane.doe@example.com", 100 | "url": "https://example.com" 101 | } 102 | ], 103 | "invocationImages": [ 104 | { 105 | "imageType": "docker", 106 | "image": "deislabs/helloworld-cnab:37db1697dab0116c78b3cc862fa105ebb380094d" 107 | } 108 | ], 109 | "images": null, 110 | "parameters": null, 111 | "credentials": null 112 | } 113 | ``` 114 | 115 | Now that we've built our very first bundle, it's time to install it - keep in mind that our very first bundle doesn't have any referenced images, parameters, or credentials: 116 | 117 | ``` 118 | $ duffle install our-first-test helloworld 119 | Executing install action... 120 | hey I am installing things over here 121 | 122 | $ duffle status our-first-test 123 | Installation Name: our-first-test 124 | Installed at: 2018-12-07 17:50:33.6694255 +0200 STD 125 | Last Modified at: 2018-12-07 17:50:35.4166926 +0200 STD 126 | Current Revision: 01CY4NT2MR7SC19AHC2ZQ7W517 127 | Bundle: helloworld 128 | Last Action Performed: install 129 | Last Action Status: success 130 | Last Action Message: 131 | Executing status action in bundle... 132 | ``` 133 | 134 | And that's it - our bundle was executed using the run script we defined earlier, and it used the _install action_ defined by the Duffle CLI, in `duffle install`. 135 | 136 | 137 | # Signed bundles 138 | 139 | By default, all bundles built by Duffle are cryptographically signed, and by default, Duffle generates a signing key for you: 140 | 141 | ``` 142 | $ duffle bundle verify helloworld 143 | Signed by "[anonymous key]" (D563 47CA C1BE BEF3 EBC4 1B75 B43F 79B2 BA39 F560) 144 | ``` 145 | 146 | You can add your own GPG secret keys to sign bundles, and share your public key so that others can verify bundles you built and signed: 147 | 148 | ``` 149 | $ duffle bundle verify 150 | Signed by "Your Name " () 151 | ``` 152 | 153 | You can see the raw signed bundle file by executing: 154 | 155 | ``` 156 | $ duffle show helloworld --raw 157 | -----BEGIN PGP SIGNED MESSAGE----- 158 | Hash: SHA256 159 | 160 | { 161 | "name": "helloworld", 162 | "version": "0.1.0", 163 | "description": "A short description of your bundle", 164 | "keywords": [ 165 | "helloworld", 166 | "cnab", 167 | "tutorial" 168 | ], 169 | "maintainers": [ 170 | { 171 | "name": "John Doe", 172 | "email": "john.doe@example.com", 173 | "url": "https://example.com" 174 | }, 175 | { 176 | "name": "Jane Doe", 177 | "email": "jane.doe@example.com", 178 | "url": "https://example.com" 179 | } 180 | ], 181 | "invocationImages": [ 182 | { 183 | "imageType": "docker", 184 | "image": "deislabs/helloworld-cnab:37db1697dab0116c78b3cc862fa105ebb380094d" 185 | } 186 | ], 187 | "images": null, 188 | "parameters": null, 189 | "credentials": null 190 | } 191 | -----BEGIN PGP SIGNATURE----- 192 | 193 | wsFcBAEBCAAQBQJcBpr9CRD6XanxV8EQggAANSoQAGjgV4NfVJwKvAfBancDGFXF 194 | AGL3kUEB/0eubPN5Ts0QXf1+k3OICdh06g+QTSrb6bM+ULwgxfUkZIDJjKGNBal4 195 | 04+/DW7MaX9WMW8L9SxKPJGwScNfk06j367kXhC0bRuKZajuHUseLJhzYGTRtwvn 196 | vAS5x5PLYzHJBosDvO9Un9MZkrTH2N1I+zNXnS+CNLauG5yAiHWSmBnxxTxU1mvS 197 | Ms2z6kGsxvF1GUrtjSnnPQ/G4g40DKQSs3NBZNO9gS0zTcaMKq5s3ZIBo1ivqu7X 198 | KSSUFOeSjDQZdNOL/ylXQgNIYBxk7LzeVr1yz8zux9dDEkYhwjHBSYf5NtXYbe1E 199 | JjvJku9SAqTZfsJvzlGLbrB5SUASvSJKS7coIVnRUTiV3UDb9kdrX9tUOEW9px13 200 | 2kasSu7q9pj2WnlptA3grQEmTvJsl9OdWH7ujHUVHTJQAN/pjMKfoGqFyLNFS96w 201 | Tw6hS1bknYMts93vN4Ub7e6DFG27cOfTnx+5dxE7zwJ2YbD6TrATJ86cfT1HVU/u 202 | JoVNPVHQLQ5u0taoS7lfL+1MsUEFrioTTUffxmww5OZ5mdPKt9TzP0UQ7X5I2R8a 203 | 9lJN7aJCyzvJcwz8TGFdOeFP1cjHqVwueniyxowdBWygkbbvTLPDDjQkjJN0Z7C+ 204 | nroJHDUoY0M0KxODijXB 205 | =Nuqt 206 | -----END PGP SIGNATURE----- 207 | ``` 208 | 209 | > This is the signed JSON file described in the specification. 210 | 211 | [bundle-runtime]: https://github.com/deislabs/cnab-spec/blob/master/103-bundle-runtime.md 212 | [bundles]: https://github.com/deislabs/bundles -------------------------------------------------------------------------------- /content/04-params-creds.md: -------------------------------------------------------------------------------- 1 | # Using parameters and credentials 2 | 3 | ## Parameters 4 | 5 | CNAB includes a method for declaring user-facing parameters that can be changed during certain operations (like installation). Parameters are specified in the build manifest (the bundle.json file). Duffle processes these as follows: 6 | 7 | - The user may specify values when invoking duffle install or similar commands. 8 | - Prior to executing the image, Duffle will read the manifest file, extract the parameter definitions, and then merge specified values and defaults into one set of finalized parameters 9 | - During startup of the image, Duffle will inject each parameter as an environment variable, following the conversion method determined by CNAB: 10 | - The variable name will be: CNAB_P_ plus the uppercase variable name - e.g. CNAB_P_SERVER_PORT (unless the destination field is set for the parameter), and the value will be a string representation of the value. 11 | 12 | A CNAB bundle.json file MAY specify zero or more parameters whose values MAY be specified by a user. 13 | 14 | As specified, values MAY be passed into the container as environment variables. If the environment variable name is specified in the destination, that name will be used: 15 | 16 | ```json 17 | "parameters": { 18 | "greeting": { 19 | "defaultValue": "hello", 20 | "type": "string", 21 | "destination": { 22 | "env": "GREETING" 23 | }, 24 | "metadata":{ 25 | "description": "this will be in $GREETING" 26 | } 27 | } 28 | } 29 | ``` 30 | 31 | The above will set `GREETING=hello`. In the case where no `destination` is set, a parameter is written as an environment variable with an automatically generated name - which can be accessed through the `$CNAB_P_` environment variable. 32 | 33 | Now let's take a look at a complete example that uses parameters. In our previous example, let's add the `parameters` section in the build manifest, `duffle.json`, so it becomes: 34 | 35 | ```json 36 | { 37 | "name": "helloworld", 38 | "version": "0.1.0", 39 | "description": "A short description of your bundle", 40 | "keywords": [ 41 | "helloworld", 42 | "cnab", 43 | "tutorial" 44 | ], 45 | "maintainers": [ 46 | { 47 | "name": "John Doe", 48 | "email": "john.doe@example.com", 49 | "url": "https://example.com" 50 | }, 51 | { 52 | "name": "Jane Doe", 53 | "email": "jane.doe@example.com", 54 | "url": "https://example.com" 55 | } 56 | ], 57 | "invocationImages": { 58 | "cnab": { 59 | "name": "cnab", 60 | "builder": "docker", 61 | "configuration": { 62 | "registry": "deislabs" 63 | } 64 | } 65 | }, 66 | "parameters": { 67 | "greeting": { 68 | "defaultValue": "hello", 69 | "type": "string", 70 | "destination": { 71 | "env": "GREETING" 72 | }, 73 | "metadata": { 74 | "description": "this will be in $GREETING" 75 | } 76 | } 77 | } 78 | } 79 | ``` 80 | 81 | At this point, you have to simply use the `$GREETING` environment variable from your `run` executable - since in the simplest case it is a Bash script, let's just print it - so our `run` script becomes: 82 | 83 | ```bash 84 | #!/bin/bash 85 | action=$CNAB_ACTION 86 | greeting=$GREETING 87 | 88 | if [[ $action == "install" ]]; then 89 | echo "hey I am installing things over here, $GREETING was passed as param" 90 | elif [[ $action == "uninstall" ]]; then 91 | echo "hey I am uninstalling things now, $GREETING was passed as param" 92 | fi 93 | ``` 94 | 95 | 96 | Now if we rebuild the bundle and install it, we can pass the parameter using `--set greeting=`: 97 | 98 | ``` 99 | $ duffle build 100 | ... 101 | $ duffle install param-test helloworld --set greeting=HELLO 102 | Executing install action... 103 | hey I am installing things over here, HELLO was passed as param 104 | $ duffle uninstall param-test --set greeting=GOODBYE 105 | Executing uninstall action... 106 | hey I am uninstalling things now, GOODBYE was passed as param 107 | ``` 108 | 109 | 110 | ## Credential sets 111 | 112 | Consider the case where a CNAB bundle named `example/myapp:1.0.0` connects to both ARM (Azure IaaS API service) and Kubernetes. Each has its own API surface which is secured by a separate set of credentials. ARM requires a periodically expiring token managed via `az`. Kubernetes stores credentialing information in a `KUBECONFIG` YAML file. 113 | 114 | When the user runs `duffle install my_example example/myapp:1.0.0`, the operations in the invocation image need to be executed with a specific set of credentials from the user. 115 | 116 | Those values must be injected from `duffle` into the invocation image - but Duffle must know which credentials to send. 117 | 118 | The situation is complicated by the following factors: 119 | 120 | - There is no predefined set of services (and thus predefined set of credentialing) specified by CNAB. 121 | - It is possible, and even likely, that a user may have more than one set of credentials for a service or backend (e.g. credentials for two different Kubernetes clusters) 122 | - Some credentials require commands to be executed on a host, such as unlocking a vault or regenerating a token 123 | - There is no standard file format for storing credentials 124 | - The consuming applications may require the credentials be submitted via different methods, including as environment variables, files, or STDIN. 125 | 126 | Duffle solves this problem using a _credential set_ - named set of credentials (or credential generators) that is managed on the local host (via `duffle`) and injected into the invocation container on demand. 127 | 128 | Now let's see how Duffle can help us generate credential sets, and how to use them. 129 | 130 | First of all, clone the bundles repository from https://github.com/deislabs/bundles: 131 | 132 | ``` 133 | $ git clone https://github.com/deislabs/bundles 134 | $ cd bundles/example-credentials 135 | $ cat bundle.json 136 | 137 | { 138 | "name": "example-credentials", 139 | "version": "0.0.1", 140 | "invocationImages": [ 141 | { 142 | "imageType": "docker", 143 | "image": "cnab/example-credentials:latest" 144 | } 145 | ], 146 | "images": [], 147 | "credentials": { 148 | "secret1": { 149 | "env" :"SECRET_ONE" 150 | }, 151 | "secret2": { 152 | "path": "/var/secret_two/data.txt" 153 | }, 154 | "secret3": { 155 | "env": "SECRET_THREE", 156 | "path": "/var/secret_three/data.txt" 157 | } 158 | } 159 | } 160 | ``` 161 | 162 | The bundle file describes what credentials it needs, and how they will be injected inside the invocation image: 163 | 164 | - `secret1` will be passed as the `$SECRET_ONE` environment variable 165 | - `secret2` and `secret3` will be created as files, inside `/var//data.txt`, as text files. 166 | 167 | At this point, we can generate a credential set based on an existing bundle file: 168 | 169 | > Note that because the bundle is unsigned, we have to pass the `--insecure` flag to `duffle`. 170 | 171 | 172 | Duffle will prompt for the source of the credential set, and you can choose a specific value, the value of an environment variable, a file path or a shell command that generates the credential set. 173 | 174 | ``` 175 | $ duffle creds generate creds -f bundle.json --insecure 176 | ? Choose a source for "secret1" specific value 177 | ? Enter a value for "secret1" 46 178 | ? Choose a source for "secret2" environment variable 179 | ? Enter a value for "secret2" PATH 180 | ? Choose a source for "secret3" file path 181 | ? Enter a value for "secret3" ~/turtles.txt 182 | 183 | $ duffle creds list 184 | NAME PATH 185 | creds /home/radu/.duffle/credentials/creds.yaml 186 | 187 | $ duffle creds show creds 188 | name: creds 189 | credentials: 190 | - name: secret1 191 | source: 192 | value: REDACTED 193 | - name: secret2 194 | source: 195 | env: PATH 196 | - name: secret3 197 | source: 198 | path: ~/turtles.txt # note that this file has to be present on your file system 199 | ``` 200 | 201 | Now we can use the credential set created when executing an action using this bundle: 202 | 203 | > Note because a known bug in `duffle`, you may have to first pull the container invocation image using `docker pull cnab/example-credentials:latest`. 204 | 205 | ``` 206 | $ duffle install cred-test2 -f bundle.json -c creds --insecure 207 | Executing install action... 208 | SECRET_ONE: 46 209 | /var/secret_two/data.txt 210 | /home/radu/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin [REDACTED - rest of the expanded $PATH environment variable] 211 | SECRET_THREE: turtles 212 | 213 | /var/secret_three/data.txt 214 | turtles 215 | ``` 216 | 217 | As this bundle simply echoes the values of the passed credential set, we can see how the credentials we passed were injected inside the invocation image. 218 | -------------------------------------------------------------------------------- /content/05-export-import.md: -------------------------------------------------------------------------------- 1 | # Exporting and Importing Cloud Native Application Bundles 2 | 3 | ## Export 4 | Consider the case where a user wants to create package that contains the bundle manifest along with the all of the necessary artifacts to execute the install/uninstall/lifecycle actions specified in the invocation images. You can use the `$ duffle export ` command to do just that. 5 | 6 | Duffle `export` allows a user to create a gzipped tar archive that contains the bundle manifest (signed or unisgned) along with all of the necessary images including the invocation images and the referenced images in the bundle. See example below 7 | 8 | ### Export Example 9 | Given a directory called `wordpress/` which contains a bundle manifest (`bundle.json` or `bundle.cnab`) file: 10 | ```console 11 | $ duffle export wordpress/ -k 12 | $ ls 13 | wordpress/ wordpress-0.2.0.tgz 14 | ``` 15 | 16 | For this example, we're passing in the `-k` flag allowing us to export an unsigned/insecure bundle. The default behavior (without `-k`) of export is to only package signed bundles. 17 | 18 | In the example, you'll find the exported artifact, a gzipped tar archive: `wordpress-0.2.0.tgz`. Unpacking that artifact results in the following directory structure: 19 | ``` 20 | wordpress-0.2.0/ 21 | bundle.json 22 | artifacts/ 23 | cnab-wordpress-0.2.0.tar 24 | ``` 25 | 26 | Duffle export gives users the ability to package up all the components of their distributed application along with the logic to manage that application leaving users with a portable artifact. 27 | 28 | ## Import 29 | 30 | Duffle import is used to import the exported artifact above along with all of the necessary images to manage the application. It unpacks the artifact and saves all of the images in the `artifacts/` to the local Docker store. 31 | 32 | ### Import Example 33 | ```console 34 | $ duffle import wordpress-0.2.0.tgz -k 35 | 36 | $ ls 37 | wordpress-0.2.0.tgz wordpress-0.2.0/ 38 | 39 | $ ls wordpress-0.2.0/ 40 | bundle.json artifacts/ 41 | 42 | $ docker images 43 | REPOSITORY TAG IMAGE ID CREATED SIZE 44 | cnab/wordpress latest 533cfdfba95a 5 hours ago 35MB 45 | -------------------------------------------------------------------------------- /content/10-porter.md: -------------------------------------------------------------------------------- 1 | # Porter 2 | Porter, the friendly neighborhood CNAB authoring tool. 3 | 4 | 5 | 6 | Duffle and CNAB are implementation details, porter is the user experience. Ideally 7 | most people are using porter, and not having to deal with what's going on under the hood. 8 | 9 | # Does Porter Replace Duffle? 10 | Nope! 😎 11 | 12 | Learn more about [when you would use porter or duffle][porter-or-duffle]. 13 | 14 | # DEMO TIME! 15 | 16 | Let's see porter in action 17 | 18 | [porter-or-duffle]: https://github.com/deislabs/porter/blob/master/docs/content/porter-or-duffle.md 19 | -------------------------------------------------------------------------------- /content/12-manifest-deps.md: -------------------------------------------------------------------------------- 1 | # Bundle Dependencies 2 | 3 | ## Reusable MySQL Bundle 4 | 5 | ```yaml 6 | name: mysql 7 | version: 0.1.0 8 | invocationImage: porter-mysql:latest 9 | 10 | credentials: 11 | - name: kubeconfig 12 | path: /root/.kube/config 13 | 14 | parameters: 15 | - name: database-name 16 | type: string 17 | default: mydb 18 | destination: 19 | env: DATABASE_NAME 20 | - name: mysql-user 21 | type: string 22 | destination: 23 | env: MYSQL_USER 24 | 25 | mixins: 26 | - helm 27 | 28 | install: 29 | - description: "Install MySQL" 30 | helm: 31 | name: porter-ci-mysql 32 | chart: stable/mysql 33 | version: 0.10.2 34 | replace: true 35 | set: 36 | mysqlDatabase: 37 | source: bundle.parameters.database-name 38 | mysqlUser: 39 | source: bundle.parameters.mysql-user 40 | outputs: 41 | - name: mysql-root-password 42 | secret: porter-ci-mysql 43 | key: mysql-root-password 44 | - name: mysql-password 45 | secret: porter-ci-mysql 46 | key: mysql-password 47 | uninstall: 48 | - description: "Uninstall MySQL" 49 | helm: 50 | name: porter-ci-mysql 51 | purge: true 52 | ``` 53 | 54 | ## Wordpress Bundle 55 | 56 | ```yaml 57 | mixins: 58 | - helm 59 | 60 | name: wordpress 61 | version: 0.1.0 62 | invocationImage: porter-wordpress:latest 63 | 64 | dependencies: 65 | - name: mysql 66 | parameters: 67 | database_name: wordpress 68 | mysql_user: wordpress 69 | 70 | credentials: 71 | - name: kubeconfig 72 | path: /root/.kube/config 73 | 74 | parameters: 75 | - name: wordpress-name 76 | type: string 77 | default: porter-ci-wordpress 78 | destination: 79 | env: WORDPRESS_NAME 80 | 81 | install: 82 | - description: "Install Wordpress" 83 | helm: 84 | name: 85 | source: bundle.parameters.wordpress-name 86 | chart: stable/wordpress 87 | replace: true 88 | set: 89 | externalDatabase.database: 90 | source: bundle.dependencies.mysql.parameters.database-name 91 | externalDatabase.host: # NOTE: Not implemented yet! 92 | source: bundle.dependencies.mysql.services.mysql 93 | externalDatabase.user: 94 | source: bundle.dependencies.mysql.parameters.mysql-user 95 | externalDatabase.password: 96 | source: bundle.dependencies.mysql.outputs.mysql-password 97 | 98 | uninstall: 99 | - description: "Uninstall Wordpress" 100 | helm: 101 | name: 102 | source: bundle.parameters.wordpress-name 103 | ``` 104 | -------------------------------------------------------------------------------- /content/12-porter-manifest.md: -------------------------------------------------------------------------------- 1 | # Porter Manifests 2 | 3 | ## Hello World 4 | 5 | ```yaml 6 | # 7 | # Standard CNAB Metadata 8 | # 9 | name: HELLO 10 | version: 0.1.0 11 | invocationImage: porter-hello:latest 12 | 13 | # 14 | # Porter specific configuration ahead 15 | # 16 | 17 | # Mixins can be pulled in to make working with other systems easier 18 | mixins: 19 | - exec 20 | 21 | # Steps to execute during the CNAB install action 22 | install: 23 | - description: "Install Hello World" 24 | exec: 25 | command: bash 26 | arguments: 27 | - -c 28 | - echo Hello World 29 | 30 | # Steps to execute during the CNAB uninstall action 31 | uninstall: 32 | - description: "Uninstall Hello World" 33 | exec: 34 | command: bash 35 | arguments: 36 | - -c 37 | - echo Goodbye World 38 | ``` 39 | 40 | ## Kubernetes Hello World 41 | 42 | ```yaml 43 | # 44 | # Standard CNAB Metadata 45 | # 46 | name: porter-azure-wordpress 47 | version: 0.1.0 48 | invocationImage: deislabs/porter-azure-wordpress:latest 49 | 50 | credentials: 51 | - name: SUBSCRIPTION_ID 52 | env: AZURE_SUBSCRIPTION_ID 53 | - name: CLIENT_ID 54 | env: AZURE_CLIENT_ID 55 | - name: TENANT_ID 56 | env: AZURE_TENANT_ID 57 | - name: CLIENT_SECRET 58 | env: AZURE_CLIENT_SECRET 59 | - name: kubeconfig 60 | path: /root/.kube/config 61 | 62 | parameters: 63 | - name: mysql_user 64 | type: string 65 | default: azureuser 66 | 67 | - name: mysql_password 68 | type: string 69 | default: "!Th1s1s4p4ss!" 70 | 71 | - name: database_name 72 | type: string 73 | default: "wordpress" 74 | 75 | # 76 | # Porter specific configuration ahead 77 | # 78 | 79 | # Mixins can be pulled in to make working with other systems easier 80 | mixins: 81 | - azure 82 | - helm 83 | 84 | # Steps to execute during the CNAB install action 85 | install: 86 | - description: "Create Azure MySQL" 87 | azure: 88 | type: mysql 89 | name: mysql-azure-porter-demo-wordpress 90 | resourceGroup: "porter-test" 91 | parameters: 92 | administratorLogin: 93 | source: bundle.parameters.mysql_user 94 | administratorLoginPassword: 95 | source: bundle.parameters.mysql_password 96 | location: "eastus" 97 | serverName: "mysql-jeremy-porter-test" 98 | version: "5.7" 99 | sslEnforcement: "Disabled" 100 | databaseName: 101 | source: bundle.parameters.database_name 102 | # Outputs from this step that should be made available to other steps in 103 | # the bundle 104 | outputs: 105 | - name: "MYSQL_HOST" 106 | key: "MYSQL_HOST" 107 | 108 | - description: "Helm Install Wordpress" 109 | helm: 110 | name: porter-ci-wordpress 111 | chart: stable/wordpress 112 | repalce: true 113 | set: 114 | mariadb.enabled: "false" 115 | externalDatabase.port: 3306 116 | readinessProbe.initialDelaySeconds: 120 117 | externalDatabase.host: 118 | # Sets externalDatabase.host to the MYSQL_HOST created in the previous step 119 | source: bundle.outputs.MYSQL_HOST 120 | externalDatabase.user: 121 | # Sets externalDatabase.user to the bundle's mysql_user parameter 122 | source: bundle.parameters.mysql_user 123 | externalDatabase.password: 124 | source: bundle.parameters.mysql_password 125 | externalDatabase.database: 126 | source: bundle.parameters.database_name 127 | 128 | # Steps to execute during the CNAB uninstall action 129 | uninstall: 130 | - description: "Uninstall Wordpress" 131 | azure: 132 | name: porter-ci-wordpress 133 | ``` 134 | -------------------------------------------------------------------------------- /content/13-porter-mixins.md: -------------------------------------------------------------------------------- 1 | # Porter Mixins 2 | 3 | Mixins understand the Porter Manifest and the CNAB spec, and translate porter steps 4 | into actions against another system such as Helm, Terraform or Azure. 5 | 6 | This is how you get to avoid writing lots of bash. 😉 7 | 8 | Initially we have 3 mixins: 9 | 10 | * exec (this ships with porter) 11 | * [helm](https://github.com/deislabs/porter-helm) 12 | * [azure](https://github.com/deislabs/porter-azure) 13 | 14 | ## Exec Mixin 15 | Execute commands and scripts. This helps you glue together steps, try stuff out, 16 | or to help you reuse existing scripts. 17 | 18 | ```yaml 19 | install: 20 | - description: "Install Hello World" 21 | exec: 22 | command: bash 23 | arguments: 24 | - -c 25 | - echo Hello World 26 | ``` 27 | 28 | ## Helm Mixin 29 | Manage helm charts. 30 | 31 | * Any secrets created by the chart are made available as outputs to the rest of the bundle. 32 | 33 | ## Azure Mixin 34 | Work with Azure Cloud resources and services. Works with: 35 | 36 | * ARM templates 37 | * Azure Container Instances (ACI) 38 | * MySQL 39 | * CosmosDB 40 | -------------------------------------------------------------------------------- /content/15-porter-installation.md: -------------------------------------------------------------------------------- 1 | # Install Porter 2 | Porter requires duffle, so make sure you have [duffle installed](02-prerequisites.md). 3 | 4 | ## MacOS 5 | 6 | ``` 7 | curl https://deislabs.blob.core.windows.net/porter/latest/install-mac.sh | bash 8 | ``` 9 | 10 | ## Linux 11 | 12 | ``` 13 | curl https://deislabs.blob.core.windows.net/porter/latest/install-linux.sh | bash 14 | ``` 15 | 16 | ## Windows 17 | 18 | ``` 19 | iwr ‘https://deislabs.blob.core.windows.net/porter/latest/install-windows.ps1' -UseBasicParsing | iex 20 | ``` 21 | -------------------------------------------------------------------------------- /content/16-exercise-1.md: -------------------------------------------------------------------------------- 1 | # Exercise: Hello World 2 | 3 | 1. Create a directory for your bundle and change into it: 4 | 5 | ``` 6 | mkdir hello 7 | cd hello 8 | ``` 9 | 10 | 2. Scaffold a bundle: 11 | 12 | ``` 13 | porter create 14 | ``` 15 | 16 | 3. Open porter.yaml and edit the Docker Registry for your bundle: 17 | 18 | ``` 19 | invocationImage: /porter-azure-wordpress:latest 20 | ``` 21 | 22 | 4. Build your bundle: 23 | 24 | ``` 25 | porter build 26 | ``` 27 | 28 | 5. Install your bundle! 29 | 30 | ``` 31 | duffle install HELLO1 -f bundle.json --insecure 32 | ``` 33 | -------------------------------------------------------------------------------- /content/16-exercise-2.md: -------------------------------------------------------------------------------- 1 | # Exercise: Create a bundle with Azure MySQL and Wordpress 2 | 3 | This example bundle will create an Azure MySQL database instance and then use the credentials of that to install Wordpress. This bundle assumes you have an Azure account and a Kubernetes cluster created. The bundle will use credentials for both. 4 | 5 | First, install Porter! 6 | 7 | ## Install 8 | 9 | ### MacOS 10 | ``` 11 | curl https://deislabs.blob.core.windows.net/porter/latest/install-mac.sh | bash 12 | ``` 13 | 14 | ### Linux 15 | ``` 16 | curl https://deislabs.blob.core.windows.net/porter/latest/install-linux.sh | bash 17 | ``` 18 | 19 | ### Windows 20 | ``` 21 | iwr "https://deislabs.blob.core.windows.net/porter/latest/install-windows.ps1" -UseBasicParsing | iex 22 | ``` 23 | 24 | Next, clone the Porter repository to get the sample bundle. 25 | 26 | ``` 27 | git clone https://github.com/deislabs/porter.git 28 | cd porter/examples/azure-mysql-wordpress 29 | ``` 30 | 31 | Open the porter.yaml file and take a look around. You'll also want to update the invocation image to reference a docker registry that you can push to. As part of the build step, you'll need to push to that registry. 32 | 33 | For example, change 34 | 35 | ``` 36 | invocationImage: deislabs/porter-azure-wordpress:latest 37 | ``` 38 | 39 | to 40 | 41 | ``` 42 | invocationImage: /porter-azure-wordpress:latest 43 | ``` 44 | 45 | 46 | Now, you can do a _porter build_ 47 | 48 | ``` 49 | porter build 50 | ``` 51 | 52 | This will generate the Dockerfile and bundle.json for the example. 53 | 54 | Now, you will need to setup your environment with the credentials for the bundle to use. To get the Azure credentials, first you'll need the Azure CLI. 55 | 56 | ## Install the Azure CLI 57 | 58 | Install `az` by following the instructions for your operating system. 59 | See the [full installation instructions](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) if yours isn't listed below. You will need az cli version 2.0.37 or greater. 60 | 61 | ### MacOS 62 | 63 | ```console 64 | brew install azure-cli 65 | ``` 66 | 67 | ### Windows 68 | 69 | Download and run the [Azure CLI Installer (MSI)](https://aka.ms/InstallAzureCliWindows). 70 | 71 | ### Ubuntu 64-bit 72 | 73 | 1. Add the azure-cli repo to your sources: 74 | ```console 75 | echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ wheezy main" | \ 76 | sudo tee /etc/apt/sources.list.d/azure-cli.list 77 | ``` 78 | 1. Run the following commands to install the Azure CLI and its dependencies: 79 | ```console 80 | sudo apt-key adv --keyserver packages.microsoft.com --recv-keys 52E16F86FEE04B979B07E28DB02C46DF417A0893 81 | sudo apt-get install apt-transport-https 82 | sudo apt-get update && sudo apt-get install azure-cli 83 | ``` 84 | 85 | 86 | Now, use the Azure CLI to obtain Azure credentials. 87 | 88 | ``` 89 | az login 90 | ``` 91 | 92 | ``` 93 | az account list -o table 94 | export AZURE_SUBSCRIPTION_ID= 95 | ``` 96 | 97 | ``` 98 | az ad sp create-for-rbac --name porter-creds -o table 99 | 100 | export AZURE_TENANT_ID= 101 | export AZURE_CLIENT_ID= 102 | export AZURE_CLIENT_SECRET= 103 | export AZURE_SUBSCRIPTION_ID= 104 | ``` 105 | 106 | Note, you'll also need to have the config for your kubernetes cluster. 107 | 108 | Now, you can use duffle to generate a credential set for the bundle. 109 | 110 | ``` 111 | duffle credentials generate azure-mysql-wordpress -f bundle.json --insecure 112 | ``` 113 | 114 | This will prompt you to for a values for credentials in the bundle. Use the environment variables you set above. 115 | 116 | Finally, you're ready to install the bundle: 117 | 118 | ``` 119 | duffle install porter-wordpress -c azure-mysql-wordpress -f bundle.json --insecure 120 | ``` 121 | 122 | **NOTE** 123 | This assumes that you have initialized helm on your cluster beforehand. Make sure to run `helm init` against it before installing the bundle. 124 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # [CNAB][cnab] / Duffle Workshop 2 | 3 | > [Cloud Native Application Bundles (CNAB)][cnab] are a standard packaging format for multi-component distributed applications. It allows packages to target different runtimes and architectures. It empowers application distributors to package applications for deployment on a wide variety of cloud platforms, cloud providers, and cloud services. It also provides the capabilities necessary for delivering multi-container applications in disconnected environments. 4 | 5 | > More in [the CNAB specification on GitHub][cnab-spec]. 6 | 7 | 8 | ## Agenda 9 | 10 | 1. [What is CNAB?][what-cnab] - high level overview of Cloud Native Application Bundles and Duffle 11 | 2. [Getting started][prerequisites] - setting up your local environment 12 | 3. [Building and signing bundles][build-sign] - building your first Cloud Native Application Bundles 13 | 4. [Using parameters and credentials][params-creds] 14 | 5. [Exporting and importing bundles][export-import] 15 | 6. [Introducing Porter][porter] 16 | 8. [Porter Manifest][porter-manifest] 17 | 8. [Bundle Dependencies][bundle-deps] 18 | 9. [Porter Mixins][porter-mixins] 19 | 7. [Porter Installation][porter-install] 20 | 9. [Exercise: Hello World][exercise-1] 21 | 10. [Exercise: Kubernetes Hello World][exercise-2] 22 | 23 | [cnab]: https://cnab.io/ 24 | [cnab-spec]: https://github.com/deislabs/cnab-spec/ 25 | [what-cnab]: https://github.com/deislabs/cnab-spec/blob/master/100-CNAB.md 26 | [prerequisites]: content/02-prerequisites.md 27 | [build-sign]: content/03-build-sign.md 28 | [params-creds]: content/04-params-creds.md 29 | [export-import]: content/05-export-import.md 30 | [porter]: content/10-porter.md 31 | [porter-install]: content/15-porter-installation.md 32 | [porter-manifest]: content/12-porter-manifest.md 33 | [porter-mixins]: content/13-porter-mixins.md 34 | [exercise-1]: content/16-exercise-1.md 35 | [exercise-2]: content/16-exercise-2.md 36 | [bundle-deps]: content/12-manifest-deps.md 37 | --------------------------------------------------------------------------------