├── .github └── workflows │ ├── build.yml │ └── publish.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── airgap ├── .dockerignore ├── README.md ├── charts │ └── whalegap │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── deployment.yaml │ │ ├── ingress.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── tests │ │ │ └── test-connection.yaml │ │ └── values.yaml └── porter.yaml ├── aks-spring-music ├── .dockerignore ├── .gitignore ├── Dockerfile.tmpl ├── README.md ├── arm │ ├── aks.json │ └── cosmosdb.json ├── charts │ └── spring-music │ │ ├── Chart.yaml │ │ ├── templates │ │ ├── deploy.yaml │ │ └── service.yaml │ │ └── values.yaml ├── helpers.sh ├── manifests │ └── rbac-config.yaml └── porter.yaml ├── azure-keyvault ├── .dockerignore ├── .gitignore ├── Dockerfile.tmpl ├── README.md ├── hellokeyvault-creds.json ├── helpers.sh ├── porter.yaml └── upsertGroup.sh ├── azure-terraform ├── .dockerignore ├── .gitignore ├── README.md ├── arm │ └── storage.json ├── porter.yaml └── terraform │ ├── cosmos-db.tf │ ├── eventhubs.tf │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── azure-wordpress ├── .gitignore ├── README.md ├── arm │ └── mysql.json ├── params.ini └── porter.yaml ├── credentials-tutorial ├── .dockerignore ├── .gitignore ├── Dockerfile.tmpl ├── README.md ├── helpers.sh └── porter.yaml ├── docker ├── .dockerignore ├── .gitignore ├── README.md └── porter.yaml ├── dockerapp ├── .gitignore ├── README.md ├── docker-compose.yml └── porter.yaml ├── exec-outputs ├── .dockerignore ├── .gitignore ├── Dockerfile.tmpl ├── README.md ├── cluster.sh ├── config.json ├── porter.yaml └── run-tests.sh ├── gke ├── .dockerignore ├── .gitignore ├── Dockerfile.tmpl ├── README.md ├── helpers.sh ├── manifests │ └── nginx │ │ ├── deployment.yaml │ │ └── service.yaml └── porter.yaml ├── go.mod ├── go.sum ├── hello ├── .dockerignore ├── .gitignore ├── README.md ├── helpers.sh └── porter.yaml ├── helm-multiarch ├── .dockerignore ├── .gitignore ├── README.md ├── charts │ └── nginx │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ ├── README.md │ │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── deployment.yaml │ │ ├── hpa.yaml │ │ ├── ingress.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── tests │ │ │ └── test-connection.yaml │ │ └── values.yaml └── porter.yaml ├── images ├── README.md ├── whalesay │ ├── Dockerfile │ ├── LICENSE │ ├── Makefile │ ├── cowsay │ └── docker.cow └── whalesayd │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── go.mod │ └── main.go ├── kubernetes ├── .dockerignore ├── .gitignore ├── README.md ├── helpers.sh ├── manifests │ └── nginx │ │ ├── deployment.yaml │ │ └── service.yaml └── porter.yaml ├── mage.go ├── mage ├── examples │ ├── examples.go │ ├── examples_test.go │ └── testdata │ │ ├── missing-name │ │ └── porter.yaml │ │ ├── missing-registry │ │ └── porter.yaml │ │ └── missing-version │ │ └── porter.yaml └── setup │ └── mixins.go ├── magefile.go ├── otel-jaeger ├── .dockerignore ├── .env ├── .gitignore ├── README.md ├── config.yaml ├── dashboard.png ├── docker-compose.yaml ├── helpers.sh └── porter.yaml ├── plugins-tutorial ├── helpers.sh └── porter.yaml ├── porter-discourse ├── .dockerignore ├── .gitignore ├── Dockerfile.tmpl ├── README.md ├── helpers.sh └── porter-discourse.yaml ├── private-assets ├── .dockerignore ├── .gitignore ├── README.md ├── build-args.txt ├── check-secrets.sh ├── helpers.sh ├── porter.yaml ├── secrets │ └── token └── template.Dockerfile └── sensitive-data ├── .dockerignore ├── .gitignore ├── README.md ├── helpers.sh └── porter.yaml /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 2 | 3 | name: Build 4 | on: [pull_request] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | - name: Set up Go 15 | uses: actions/setup-go@v5 16 | with: 17 | go-version-file: go.mod 18 | - name: Set up Mage 19 | run: go run mage.go ConfigureAgent 20 | - name: Build 21 | run: mage -v Build 22 | - name: Test 23 | run: mage -v Test 24 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 2 | 3 | name: Publish 4 | on: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | publish: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | - name: Set up Go 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version-file: go.mod 21 | - name: Set up Mage 22 | run: go run mage.go ConfigureAgent 23 | - name: Build 24 | run: mage -v Build 25 | - name: Test 26 | run: mage -v Test 27 | - name: Docker Login 28 | uses: docker/login-action@v3 29 | with: 30 | registry: ghcr.io 31 | username: ${{ github.actor }} 32 | password: ${{ secrets.GITHUB_TOKEN }} 33 | - name: Publish 34 | run: mage -v Publish 35 | env: 36 | PORTER_REGISTRY: ghcr.io/getporter 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cnab/ 2 | bin/ 3 | .vscode/ 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported to project admins via email - Carolyn Van Slyck (`me@carolynvanslyck.com`) 59 | - or via direct message in [Slack] to Carolyn Van Slyck (`@carolynvs`) or Jeremy Rickard (`@Jeremy Rickard`). 60 | All complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 73 | 74 | [homepage]: https://www.contributor-covenant.org 75 | [slack]: https://porter.sh/community#slack 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | Have you created a bundle that you would like to share as an example? 4 | In your pull request: 5 | 6 | 1. Create a directory in this repository with a unique name that identifies the use case for the bundle. 7 | 1. Put your porter.yaml and supporting files in that directory. 8 | 1. Include a README.md that explains what the example demonstrates, what the bundle does, and how to use the bundle. 9 | 1. Include two tables: one for parameters and another for credentials. This helps people see info about the bundle without having to run porter explain. 10 | 11 | ## Build an example 12 | 13 | To build an example just like we do in CI, 14 | 15 | ``` 16 | mage BuildExample NAME 17 | ``` 18 | 19 | Run the following command to build all examples: 20 | 21 | ``` 22 | mage Build 23 | ``` 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2020 Porter Authors 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example Porter Bundles 2 | 3 | This repository contains example bundles that demonstrate various useful bundle tricks. 4 | 5 | * [Contribute an Example](./CONTRIBUTING.md) 6 | * [Try out Porter with an example](#try) 7 | * [Learn how to use Porter with an example](#learn) 8 | 9 | ## Try 10 | 11 | These are bundles that are intended for you to use to try out Porter and actually install an application. 12 | 13 | * [Azure Key Vault](/azure-keyvault/) 14 | * [Jaeger with Open Telemetry](/otel-jaeger/) 15 | 16 | ## Learn 17 | 18 | These are bundles that pure examples, demonstrating a technique about how to write a bundle. 19 | They aren't intended to be used to install an application (see above). 20 | 21 | * [Airgap](/airgap/) demonstrates how to write a bundle that can be installed in an airgapped or isolated network. 22 | * [AKS Spring Music](/aks-spring-music/) demonstrates how to use multiple mixins together in bundle. 23 | * [Azure Terraform](/azure-terraform/) demonstrates how to use Terraform in a bundle and persist Terraform's state in Azure Blob Storage. 24 | * [Azure Wordpres](/azure-wordpress/) demonstrates how to deploy Wordpress to Azure using the arm mixin. 25 | * [Credentials Tutorial](/credentials-tutorial/) is the source code for the [Credentials QuickStart](https://getporter.org/quickstart/credentials). 26 | * [Docker](/docker/) demonstrates how to use Docker from inside a bundle. 27 | * [Docker App](/dockerapp/) demonstrates how to convert from a Docker App to a Porter bundle with Docker Compose. 28 | * [Exec Mixin Outputs](/exec-outputs) demonstrates how to save outputs generated by the exec mixin. 29 | * [GKE](/gke/) demonstrates how to use GKE cluster credentials with a bundle. 30 | * [Hello](/hello/) is a hello world bundle for trying out Porter. 31 | * [Helm Multi-Arch](/helm-multiarch/) demonstrates how Porter can deploy a multi-arch image with Helm. 32 | * [Kubernetes](/kubernetes/) demonstrates how to use a Kubernetes kubeconfig with a bundle. 33 | * [Plugings Tutorial](/plugins-tutorial/) is the source code for the [Plugins Tutorial](https://getporter.org/plugins/tutorial/). 34 | * [Sensitive Data](/sensitive-data/) is a example bundle for trying out Porter to work with bundles that contains sensitive data. 35 | -------------------------------------------------------------------------------- /airgap/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile.tmpl 2 | whalegap.tgz 3 | -------------------------------------------------------------------------------- /airgap/README.md: -------------------------------------------------------------------------------- 1 | # Example: Airgapped Environments 2 | 3 | Try out this example at https://porter.sh/examples/airgap/. -------------------------------------------------------------------------------- /airgap/charts/whalegap/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | .vscode/ 23 | -------------------------------------------------------------------------------- /airgap/charts/whalegap/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "1.0" 3 | description: Whalesay as a service 4 | name: whalegap 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /airgap/charts/whalegap/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "whalegap.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "whalegap.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "whalegap.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "whalegap.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | echo "Visit http://127.0.0.1:8080 to use your application" 20 | kubectl port-forward $POD_NAME 8080:80 21 | {{- end }} 22 | -------------------------------------------------------------------------------- /airgap/charts/whalegap/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "whalegap.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "whalegap.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "whalegap.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Common labels 36 | */}} 37 | {{- define "whalegap.labels" -}} 38 | app.kubernetes.io/name: {{ include "whalegap.name" . }} 39 | helm.sh/chart: {{ include "whalegap.chart" . }} 40 | app.kubernetes.io/instance: {{ .Release.Name }} 41 | {{- if .Chart.AppVersion }} 42 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 43 | {{- end }} 44 | app.kubernetes.io/managed-by: {{ .Release.Service }} 45 | {{- end -}} 46 | 47 | {{/* 48 | Create the name of the service account to use 49 | */}} 50 | {{- define "whalegap.serviceAccountName" -}} 51 | {{- if .Values.serviceAccount.create -}} 52 | {{ default (include "whalegap.fullname" .) .Values.serviceAccount.name }} 53 | {{- else -}} 54 | {{ default "default" .Values.serviceAccount.name }} 55 | {{- end -}} 56 | {{- end -}} 57 | -------------------------------------------------------------------------------- /airgap/charts/whalegap/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "whalegap.fullname" . }} 5 | labels: 6 | {{ include "whalegap.labels" . | indent 4 }} 7 | spec: 8 | replicas: {{ .Values.replicaCount }} 9 | selector: 10 | matchLabels: 11 | app.kubernetes.io/name: {{ include "whalegap.name" . }} 12 | app.kubernetes.io/instance: {{ .Release.Name }} 13 | template: 14 | metadata: 15 | labels: 16 | app.kubernetes.io/name: {{ include "whalegap.name" . }} 17 | app.kubernetes.io/instance: {{ .Release.Name }} 18 | spec: 19 | {{- with .Values.imagePullSecrets }} 20 | imagePullSecrets: 21 | {{- toYaml . | nindent 8 }} 22 | {{- end }} 23 | serviceAccountName: {{ template "whalegap.serviceAccountName" . }} 24 | securityContext: 25 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 26 | containers: 27 | - name: {{ .Chart.Name }} 28 | securityContext: 29 | {{- toYaml .Values.securityContext | nindent 12 }} 30 | image: "{{ .Values.image.repository }}@{{ .Values.image.digest }}" 31 | imagePullPolicy: {{ .Values.image.pullPolicy }} 32 | env: 33 | - name: DEFAULT_MESSAGE 34 | value: "{{ .Values.msg }}" 35 | ports: 36 | - name: http 37 | containerPort: 8080 38 | protocol: TCP 39 | livenessProbe: 40 | httpGet: 41 | path: / 42 | port: 8080 43 | readinessProbe: 44 | httpGet: 45 | path: / 46 | port: 8080 47 | resources: 48 | {{- toYaml .Values.resources | nindent 12 }} 49 | {{- with .Values.nodeSelector }} 50 | nodeSelector: 51 | {{- toYaml . | nindent 8 }} 52 | {{- end }} 53 | {{- with .Values.affinity }} 54 | affinity: 55 | {{- toYaml . | nindent 8 }} 56 | {{- end }} 57 | {{- with .Values.tolerations }} 58 | tolerations: 59 | {{- toYaml . | nindent 8 }} 60 | {{- end }} 61 | -------------------------------------------------------------------------------- /airgap/charts/whalegap/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "whalegap.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 5 | apiVersion: networking.k8s.io/v1beta1 6 | {{- else -}} 7 | apiVersion: extensions/v1beta1 8 | {{- end }} 9 | kind: Ingress 10 | metadata: 11 | name: {{ $fullName }} 12 | labels: 13 | {{ include "whalegap.labels" . | indent 4 }} 14 | {{- with .Values.ingress.annotations }} 15 | annotations: 16 | {{- toYaml . | nindent 4 }} 17 | {{- end }} 18 | spec: 19 | {{- if .Values.ingress.tls }} 20 | tls: 21 | {{- range .Values.ingress.tls }} 22 | - hosts: 23 | {{- range .hosts }} 24 | - {{ . | quote }} 25 | {{- end }} 26 | secretName: {{ .secretName }} 27 | {{- end }} 28 | {{- end }} 29 | rules: 30 | {{- range .Values.ingress.hosts }} 31 | - host: {{ .host | quote }} 32 | http: 33 | paths: 34 | {{- range .paths }} 35 | - path: {{ . }} 36 | backend: 37 | serviceName: {{ $fullName }} 38 | servicePort: {{ $svcPort }} 39 | {{- end }} 40 | {{- end }} 41 | {{- end }} 42 | -------------------------------------------------------------------------------- /airgap/charts/whalegap/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "whalegap.fullname" . }} 5 | labels: 6 | {{ include "whalegap.labels" . | indent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: 8080 12 | protocol: TCP 13 | name: http 14 | selector: 15 | app.kubernetes.io/name: {{ include "whalegap.name" . }} 16 | app.kubernetes.io/instance: {{ .Release.Name }} 17 | -------------------------------------------------------------------------------- /airgap/charts/whalegap/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ template "whalegap.serviceAccountName" . }} 6 | labels: 7 | {{ include "whalegap.labels" . | indent 4 }} 8 | {{- end -}} 9 | -------------------------------------------------------------------------------- /airgap/charts/whalegap/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "whalegap.fullname" . }}-test-connection" 5 | labels: 6 | {{ include "whalegap.labels" . | indent 4 }} 7 | annotations: 8 | "helm.sh/hook": test-success 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "whalegap.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /airgap/charts/whalegap/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for whalegap. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | # Message the whale should say 6 | msg: "Whale aren't you special?" 7 | 8 | replicaCount: 1 9 | 10 | image: 11 | repository: "ghcr.io/getporter/examples/images/whalesayd" 12 | digest: "sha256:18c099989986f61c0678b8040cc8d2e80ba20ecd096da51f8da3a31c5d1138b8" 13 | pullPolicy: IfNotPresent 14 | 15 | imagePullSecrets: [] 16 | nameOverride: "" 17 | fullnameOverride: "" 18 | 19 | serviceAccount: 20 | # Specifies whether a service account should be created 21 | create: true 22 | # The name of the service account to use. 23 | # If not set and create is true, a name is generated using the fullname template 24 | name: 25 | 26 | podSecurityContext: {} 27 | # fsGroup: 2000 28 | 29 | securityContext: {} 30 | # capabilities: 31 | # drop: 32 | # - ALL 33 | # readOnlyRootFilesystem: true 34 | # runAsNonRoot: true 35 | # runAsUser: 1000 36 | 37 | service: 38 | type: ClusterIP 39 | port: 80 40 | 41 | ingress: 42 | enabled: false 43 | annotations: {} 44 | # kubernetes.io/ingress.class: nginx 45 | # kubernetes.io/tls-acme: "true" 46 | hosts: 47 | - host: chart-example.local 48 | paths: [] 49 | 50 | tls: [] 51 | # - secretName: chart-example-tls 52 | # hosts: 53 | # - chart-example.local 54 | 55 | resources: {} 56 | # We usually recommend not to specify default resources and to leave this as a conscious 57 | # choice for the user. This also increases chances charts run on environments with little 58 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 59 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 60 | # limits: 61 | # cpu: 100m 62 | # memory: 128Mi 63 | # requests: 64 | # cpu: 100m 65 | # memory: 128Mi 66 | 67 | nodeSelector: {} 68 | 69 | tolerations: [] 70 | 71 | affinity: {} 72 | -------------------------------------------------------------------------------- /airgap/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/whalegap 3 | version: 0.2.1 4 | description: "An example bundle that demonstrates how to sneak a whale-sized bundle through an airgap" 5 | registry: ghcr.io/getporter 6 | 7 | parameters: 8 | - name: release 9 | description: helm release name 10 | type: string 11 | default: whalegap 12 | - name: msg 13 | description: a message for the whales to speak 14 | type: string 15 | default: "whale hello there!" 16 | 17 | credentials: 18 | - name: kubeconfig 19 | path: /home/nonroot/.kube/config 20 | 21 | images: 22 | whalesayd: 23 | description: "Whalesay as a service" 24 | imageType: "docker" 25 | repository: "ghcr.io/getporter/examples/images/whalesayd" 26 | digest: "sha256:18c099989986f61c0678b8040cc8d2e80ba20ecd096da51f8da3a31c5d1138b8" 27 | 28 | mixins: 29 | - helm3 30 | 31 | install: 32 | - helm3: 33 | description: "Install WhaleGap" 34 | name: "{{ bundle.parameters.release }}" 35 | chart: ./charts/whalegap 36 | replace: true 37 | set: 38 | msg: "{{ bundle.parameters.msg }}" 39 | image.repository: "{{ bundle.images.whalesayd.repository }}" 40 | image.digest: "{{ bundle.images.whalesayd.digest }}" 41 | 42 | upgrade: 43 | - helm3: 44 | description: "Upgrade WhaleGap" 45 | name: "{{ bundle.parameters.release }}" 46 | chart: ./charts/whalegap 47 | set: 48 | msg: "{{ bundle.parameters.msg }}" 49 | image.repository: "{{ bundle.images.whalesayd.repository }}" 50 | image.digest: "{{ bundle.images.whalesayd.digest }}" 51 | 52 | uninstall: 53 | - helm3: 54 | description: "Uninstall WhaleGap" 55 | purge: true 56 | releases: 57 | - "{{ bundle.parameters.release }}" 58 | -------------------------------------------------------------------------------- /aks-spring-music/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl -------------------------------------------------------------------------------- /aks-spring-music/.gitignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .cnab/ -------------------------------------------------------------------------------- /aks-spring-music/Dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | FROM debian:stable-slim 2 | 3 | ARG BUNDLE_DIR 4 | 5 | RUN apt-get update -y \ 6 | && apt-get install curl apt-transport-https lsb-release gnupg -y \ 7 | && curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/microsoft.asc.gpg > /dev/null \ 8 | && AZ_REPO=$(lsb_release -cs) \ 9 | && echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main" | tee /etc/apt/sources.list.d/azure-cli.list \ 10 | && apt-get update \ 11 | && apt-get install azure-cli 12 | 13 | COPY . ${BUNDLE_DIR} 14 | -------------------------------------------------------------------------------- /aks-spring-music/README.md: -------------------------------------------------------------------------------- 1 | # CNAB Bundle with Porter - Spring Music Demo App 2 | 3 | This bundle demonstrates advanced use cases for Porter. 4 | 5 | The bundle leverages a base Dockerfile (Dockerfile.tmpl) to customize the resulting invocation image for the bundle by first installing the `azure cli` so that it can be used by the `exec` mixin. It then uses 4 mixins to access your Azure subscription and deploy the app. These values need to be updated in the porter.yaml. 6 | 7 | * The `arm` mixin is used to create an AKS cluster using ARM. This requires subscription and tenant info. 8 | * The `exec` mixin uses an Azure Service Principal to access via the CLI and install Helm's Tiller into an AKS cluster. 9 | * The `kubernetes` mixin applys RBAC policies for Helm 10 | * The `helm3` mixin deploys the chart into the AKS cluster. 11 | 12 | 13 | ### Prerequisites 14 | 15 | - Porter on local machine. See these helpful [installation instructions](https://porter.sh/install) 16 | - Docker on local machine (eg - Docker for Mac) 17 | - Bash 18 | - Azure service principal with rights to create a RG, AKS, Cosmos, etc. 19 | 20 | ```bash 21 | az ad sp create-for-rbac --name ServicePrincipalName 22 | ``` 23 | 24 | More details here: https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest 25 | 26 | - Follow the process in the porter docs to add these credentials in your config. 27 | 28 | 29 | ### Build / Install this bundle 30 | 31 | * Setup credentials with Porter 32 | 33 | The bundle will use the service principal created above to interact with Azure. Generate a credential using the `porter credentials generate` command: 34 | 35 | ```bash 36 | porter credentials generate azure 37 | ``` 38 | 39 | * Update params for your deployment 40 | * change the `registry` to match your Docker Hub account 41 | * Cosmos and AKS names must be unique. You can either edit the `porter.yaml` file default values (starting on line 90) or you can supply the with the porter CLI as shown below. 42 | 43 | * Build the innvocation image 44 | 45 | ```bash 46 | porter build 47 | ``` 48 | 49 | * Install the bundle 50 | 51 | ```bash 52 | export INSTALL_ID=314 53 | porter install -c azure \ 54 | --param app-resource-group=spring-music-demo-$INSTALL_ID \ 55 | --param aks-resource-group=spring-music-demo-$INSTALL_ID \ 56 | --param aks-cluster-name=briar-aks-spring-$INSTALL_ID \ 57 | --param cosmosdb-service-name=briarspringmusic$INSTALL_ID \ 58 | --param azure-location=eastus 59 | ``` 60 | -------------------------------------------------------------------------------- /aks-spring-music/arm/aks.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "clusterName": { 6 | "type": "string", 7 | "defaultValue": "[concat('cnab-aks-', uniqueString(resourceGroup().id))]", 8 | "metadata": { 9 | "description": "AKS cluster name. Defaults to cnab-aks-xxxxxxxxxxxxx, where xxx... is a unique string based of the hash of your resource group id." 10 | } 11 | }, 12 | "location": { 13 | "type": "string", 14 | "defaultValue": "[resourceGroup().location]", 15 | "metadata": { 16 | "description": "The location of the Managed Cluster resource." 17 | } 18 | }, 19 | "dnsPrefix": { 20 | "type": "string", 21 | "defaultValue": "[concat('cnab-aks-', uniqueString(resourceGroup().id))]", 22 | "metadata": { 23 | "description": "Optional DNS prefix to use with hosted Kubernetes API server FQDN." 24 | } 25 | }, 26 | "osDiskSizeGB": { 27 | "type": "int", 28 | "defaultValue": 0, 29 | "metadata": { 30 | "description": "Disk size (in GB) to provision for each of the agent pool nodes. This value ranges from 0 to 1023. Specifying 0 will apply the default disk size for that agentVMSize." 31 | }, 32 | "minValue": 0, 33 | "maxValue": 1023 34 | }, 35 | "agentCount": { 36 | "type": "int", 37 | "defaultValue": 3, 38 | "metadata": { 39 | "description": "The number of nodes for the cluster." 40 | }, 41 | "minValue": 1, 42 | "maxValue": 50 43 | }, 44 | "agentVMSize": { 45 | "type": "string", 46 | "defaultValue": "Standard_DS2_v2", 47 | "metadata": { 48 | "description": "The size of the Virtual Machine." 49 | } 50 | }, 51 | "servicePrincipalClientId": { 52 | "metadata": { 53 | "description": "Client ID (used by cloudprovider)" 54 | }, 55 | "type": "securestring" 56 | }, 57 | "servicePrincipalClientSecret": { 58 | "metadata": { 59 | "description": "The Service Principal Client Secret." 60 | }, 61 | "type": "securestring" 62 | }, 63 | "osType": { 64 | "type": "string", 65 | "defaultValue": "Linux", 66 | "allowedValues": [ 67 | "Linux" 68 | ], 69 | "metadata": { 70 | "description": "The type of operating system." 71 | } 72 | }, 73 | "kubernetesVersion": { 74 | "type": "string", 75 | "defaultValue": "1.15.7", 76 | "metadata": { 77 | "description": "The version of Kubernetes." 78 | } 79 | } 80 | }, 81 | "resources": [ 82 | { 83 | "apiVersion": "2018-03-31", 84 | "type": "Microsoft.ContainerService/managedClusters", 85 | "location": "[parameters('location')]", 86 | "name": "[parameters('clusterName')]", 87 | "properties": { 88 | "kubernetesVersion": "[parameters('kubernetesVersion')]", 89 | "dnsPrefix": "[parameters('dnsPrefix')]", 90 | "agentPoolProfiles": [ 91 | { 92 | "name": "agentpool", 93 | "osDiskSizeGB": "[parameters('osDiskSizeGB')]", 94 | "count": "[parameters('agentCount')]", 95 | "vmSize": "[parameters('agentVMSize')]", 96 | "osType": "[parameters('osType')]", 97 | "storageProfile": "ManagedDisks" 98 | } 99 | ], 100 | "servicePrincipalProfile": { 101 | "clientId": "[parameters('servicePrincipalClientId')]", 102 | "Secret": "[parameters('servicePrincipalClientSecret')]" 103 | } 104 | } 105 | } 106 | ], 107 | "outputs": { 108 | "controlPlaneFQDN": { 109 | "type": "string", 110 | "value": "[reference(parameters('clusterName')).fqdn]" 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /aks-spring-music/arm/cosmosdb.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "name": { 6 | "type": "string", 7 | "defaultValue": "[concat('cnab-cosmos-', uniqueString(resourceGroup().id))]", 8 | "metadata": { 9 | "description": "Cosmos DB account name. Defaults to cnab-cosmos-xxxxxxxxxxxxx, where xxx... is a unique string based of the hash of your resource group id." 10 | } 11 | }, 12 | "location": { 13 | "type": "string", 14 | "defaultValue": "[resourceGroup().location]", 15 | "metadata": { 16 | "description": "Location for the Cosmos DB account. Defaults to the resource group location." 17 | } 18 | }, 19 | "apiVersion": { 20 | "type": "string", 21 | "defaultValue": "2016-03-31", 22 | "metadata": { 23 | "description": "API Version. Defaults to '2016-03-31'." 24 | } 25 | }, 26 | "kind": { 27 | "type": "string", 28 | "defaultValue": "MongoDB", 29 | "metadata": { 30 | "description": "The type/kind of API for the Cosmos Database. Defaults to MongoDB." 31 | } 32 | }, 33 | "mongoCapabilities": { 34 | "type": "array", 35 | "defaultValue": [ 36 | { 37 | "name": "EnableAggregationPipeline" 38 | }, 39 | { 40 | "name": "MongoDBv3.4" 41 | } 42 | ], 43 | "metadata": { 44 | "description": "MongoDB API capabilities that should be enabled by default." 45 | } 46 | }, 47 | "enableMongoCapabilities": { 48 | "type": "array", 49 | "defaultValue": "[if(equals(parameters('kind'), 'MongoDB'), parameters('mongoCapabilities'), '[]')]", 50 | "metadata": { 51 | "description": "Conditional to enable api specific capabilities if MongoDB is the API chosen." 52 | } 53 | } 54 | }, 55 | "variables": {}, 56 | "resources": [ 57 | { 58 | "apiVersion": "[parameters('apiVersion')]", 59 | "kind": "[parameters('kind')]", 60 | "type": "Microsoft.DocumentDB/databaseAccounts", 61 | "name": "[parameters('name')]", 62 | "location": "[parameters('location')]", 63 | "properties": { 64 | "databaseAccountOfferType": "Standard", 65 | "locations": [ 66 | { 67 | "id": "[concat(parameters('name'), '-', parameters('location'))]", 68 | "failoverPriority": 0, 69 | "locationName": "[parameters('location')]" 70 | } 71 | ], 72 | "enableMultipleWriteLocations": true, 73 | "isVirtualNetworkFilterEnabled": false, 74 | "virtualNetworkRules": [], 75 | "dependsOn": [], 76 | "capabilities": "[parameters('enableMongoCapabilities')]" 77 | } 78 | } 79 | ], 80 | "outputs": { 81 | "HOST": { 82 | "type": "string", 83 | "value": "[reference(parameters('name')).documentEndpoint]" 84 | }, 85 | "primary_key": { 86 | "type": "string", 87 | "value": "[listKeys(resourceId('Microsoft.DocumentDb/databaseAccounts', parameters('name')), parameters('apiVersion')).primaryMasterKey]" 88 | }, 89 | "connection_string": { 90 | "type": "string", 91 | "value": "[listConnectionStrings(resourceId('Microsoft.DocumentDb/databaseAccounts', parameters('name')), parameters('apiVersion')).connectionStrings[0].connectionString]" 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /aks-spring-music/charts/spring-music/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: spring-music 2 | home: https://github.com/cloudfoundry-samples/spring-music 3 | version: 1.0 4 | description: Spring Music sample app for CNAB bundle demo 5 | maintainers: 6 | - name: chzbrgr71 7 | email: brianisrunning@gmail.com 8 | -------------------------------------------------------------------------------- /aks-spring-music/charts/spring-music/templates/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: spring-music 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: spring-music 9 | replicas: {{.Values.deploy.replicas}} 10 | template: 11 | metadata: 12 | labels: 13 | app: spring-music 14 | spec: 15 | containers: 16 | - name: spring-music 17 | image: "{{.Values.deploy.image}}:{{.Values.deploy.imageTag}}" 18 | imagePullPolicy: Always 19 | resources: 20 | requests: 21 | memory: "512Mi" 22 | cpu: "1.0" 23 | limits: 24 | memory: "1024Mi" 25 | cpu: "2.0" 26 | ports: 27 | - containerPort: {{.Values.deploy.containerPort}} 28 | env: 29 | - name: RUNTIME 30 | value: kubernetes 31 | - name: SPRING_PROFILES_ACTIVE 32 | value: cosmosdb 33 | - name: COSMOSDB_URI 34 | value: "{{.Values.deploy.cosmosConnectString}}" -------------------------------------------------------------------------------- /aks-spring-music/charts/spring-music/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: spring-music 5 | labels: 6 | app: spring-music 7 | spec: 8 | type: "{{.Values.service.type}}" 9 | ports: 10 | - name: http 11 | protocol: TCP 12 | port: {{.Values.service.port}} 13 | targetPort: {{.Values.service.targetPort}} 14 | selector: 15 | app: spring-music -------------------------------------------------------------------------------- /aks-spring-music/charts/spring-music/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for chart 2 | 3 | deploy: 4 | replicas: 1 5 | image: "chzbrgr71/spring-music" 6 | imageTag: "v4" 7 | containerPort: 8080 8 | cosmosConnectString: "dynamic" 9 | 10 | service: 11 | type: LoadBalancer 12 | port: 80 13 | targetPort: 8080 -------------------------------------------------------------------------------- /aks-spring-music/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | uninstall() { 5 | echo App should be uninstalled here, but it is not 6 | } 7 | 8 | # Call the requested function and pass the arguments as-is 9 | "$@" 10 | -------------------------------------------------------------------------------- /aks-spring-music/manifests/rbac-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: tiller 5 | namespace: kube-system 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1beta1 8 | kind: ClusterRoleBinding 9 | metadata: 10 | name: tiller 11 | roleRef: 12 | apiGroup: rbac.authorization.k8s.io 13 | kind: ClusterRole 14 | name: cluster-admin 15 | subjects: 16 | - kind: ServiceAccount 17 | name: tiller 18 | namespace: kube-system -------------------------------------------------------------------------------- /aks-spring-music/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/aks-spring-music 3 | version: 0.2.0 4 | description: "Spring Music Demo app with Azure Cosmos DB" 5 | dockerfile: Dockerfile.tmpl 6 | registry: ghcr.io/getporter 7 | 8 | mixins: 9 | - exec 10 | - helm3 11 | - arm 12 | - kubernetes 13 | 14 | install: 15 | - arm: 16 | description: "Create AKS" 17 | type: arm 18 | template: "arm/aks.json" 19 | name: "{{ bundle.parameters.aks-cluster-name }}" 20 | resourceGroup: "{{ bundle.parameters.aks-resource-group }}" 21 | parameters: 22 | clusterName: "{{ bundle.parameters.aks-cluster-name }}" 23 | servicePrincipalClientId: "{{ bundle.credentials.CLIENT_ID}}" 24 | servicePrincipalClientSecret: "{{ bundle.credentials.CLIENT_SECRET}}" 25 | location: "{{ bundle.parameters.azure-location }}" 26 | 27 | - exec: 28 | description: "Azure CLI login" 29 | command: "az" 30 | arguments: 31 | - "login" 32 | - "--service-principal" 33 | - "--username" 34 | - "{{ bundle.credentials.CLIENT_ID}}" 35 | - "--password" 36 | - "{{ bundle.credentials.CLIENT_SECRET}}" 37 | - "--tenant" 38 | - "{{ bundle.credentials.TENANT_ID}}" 39 | 40 | - exec: 41 | description: "Azure CLI set subscription" 42 | command: "az" 43 | arguments: 44 | - "account" 45 | - "set" 46 | - "-s" 47 | - "{{ bundle.credentials.SUBSCRIPTION_ID}}" 48 | 49 | - exec: 50 | description: "Azure CLI AKS get-credentials" 51 | command: "az" 52 | arguments: 53 | - "aks" 54 | - "get-credentials" 55 | - "--resource-group" 56 | - "{{ bundle.parameters.aks-resource-group }}" 57 | - "--name" 58 | - "{{ bundle.parameters.aks-cluster-name }}" 59 | 60 | - kubernetes: 61 | description: "Add RBAC roles for Tiller" 62 | manifests: 63 | - /cnab/app/manifests 64 | wait: true 65 | 66 | - exec: 67 | description: "Initialize helm on cluster" 68 | command: "helm" 69 | arguments: 70 | - "init" 71 | - "--service-account" 72 | - "tiller" 73 | - "--upgrade" 74 | 75 | - arm: 76 | description: "Create Azure Cosmos DB" 77 | type: arm 78 | template: "arm/cosmosdb.json" 79 | name: aks-spring-music-cosmos 80 | resourceGroup: "{{ bundle.parameters.app-resource-group }}" 81 | parameters: 82 | name: "{{ bundle.parameters.cosmosdb-service-name }}" 83 | kind: "MongoDB" 84 | location: "{{ bundle.parameters.azure-location }}" 85 | outputs: 86 | - name: "COSMOSDB_HOST" 87 | key: "HOST" 88 | - name: "COSMOSDB_KEY" 89 | key: "primary_key" 90 | - name: "COSMOSDB_CONNECTION_STRING" 91 | key: "connection_string" 92 | 93 | - helm3: 94 | description: "Helm Install Spring Music Demo App" 95 | name: aks-spring-music-helm 96 | chart: /cnab/app/charts/spring-music 97 | replace: true 98 | set: 99 | deploy.cosmosConnectString: "{{ bundle.outputs.COSMOSDB_CONNECTION_STRING }}" 100 | 101 | uninstall: 102 | - exec: 103 | description: "Uninstall Spring Music Demo" 104 | command: ./helpers.sh 105 | arguments: 106 | - uninstall 107 | 108 | credentials: 109 | - name: SUBSCRIPTION_ID 110 | env: AZURE_SUBSCRIPTION_ID 111 | - name: CLIENT_ID 112 | env: AZURE_CLIENT_ID 113 | - name: TENANT_ID 114 | env: AZURE_TENANT_ID 115 | - name: CLIENT_SECRET 116 | env: AZURE_CLIENT_SECRET 117 | 118 | parameters: 119 | - name: app-resource-group 120 | type: string 121 | default: aks-spring-music-demo 122 | env: APP_RESOURCE_GROUP 123 | - name: aks-resource-group 124 | type: string 125 | default: aks-spring-music-demo 126 | env: AKS_RESOURCE_GROUP 127 | - name: aks-cluster-name 128 | type: string 129 | default: my-aks-spring 130 | env: AKS_CLUSTER_NAME 131 | - name: cosmosdb-service-name 132 | type: string 133 | default: myspringmusic 134 | env: COSMOSDB_SERVICE_NAME 135 | - name: azure-location 136 | type: string 137 | default: eastus 138 | env: AZURE_LOCATION 139 | -------------------------------------------------------------------------------- /azure-keyvault/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl 5 | -------------------------------------------------------------------------------- /azure-keyvault/.gitignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .cnab/ 3 | -------------------------------------------------------------------------------- /azure-keyvault/Dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/amd64 debian:bullseye-slim 2 | # If someone builds on a mac m1, the powershell package is not available 3 | # so force always building as amd64 4 | 5 | # PORTER_INIT 6 | 7 | RUN apt-get update && apt-get install -y ca-certificates && apt-get install -y apt-transport-https && apt-get install -y wget 8 | 9 | RUN wget https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb 10 | RUN dpkg -i packages-microsoft-prod.deb 11 | RUN apt-get update && apt-get install -y powershell 12 | # This is a template Dockerfile for the bundle's invocation image 13 | # You can customize it to use different base images, install tools and copy configuration files. 14 | # 15 | # Porter will use it as a template and append lines to it for the mixins 16 | # and to set the CMD appropriately for the CNAB specification. 17 | # 18 | # Add the following line to porter.yaml to instruct Porter to use this template 19 | # dockerfile: Dockerfile.tmpl 20 | 21 | # You can control where the mixin's Dockerfile lines are inserted into this file by moving "# PORTER_MIXINS" line 22 | # another location in this file. If you remove that line, the mixins generated content is appended to this file. 23 | # PORTER_MIXINS 24 | 25 | # Use the BUNDLE_DIR build argument to copy files into the bundle 26 | COPY . $BUNDLE_DIR -------------------------------------------------------------------------------- /azure-keyvault/README.md: -------------------------------------------------------------------------------- 1 | # Azure Key Vault 2 | 3 | ## Running the Bundle 4 | 5 | ### Setup 6 | 7 | Generate credentials by going through the interactive cli. 8 | 9 | ```sh 10 | porter generate credentials -r ghcr.io/asalbers/hello-keyvault:v0.1.2 11 | ``` 12 | Sample output from generate credentials shows sourcing from environment variables. 13 | 14 | ```sh 15 | Building bundle ===> 16 | Copying porter runtime ===> 17 | Copying mixins ===> 18 | Copying mixin exec ===> 19 | Copying mixin az ===> 20 | 21 | Generating Dockerfile =======> 22 | 23 | Writing Dockerfile =======> 24 | 25 | Starting Invocation Image Build (ghcr.io/asalbers/hello-keyvault-installer:v0.1.2) =======> 26 | Generating new credential hello-keyvault from bundle hello-keyvault 27 | ==> 4 credentials required for bundle hello-keyvault 28 | ? How would you like to set credential "AZURE_CLIENT_ID" 29 | [Use arrows to move, space to select, type to filter] 30 | secret 31 | specific value 32 | > environment variable 33 | file path 34 | shell command 35 | Enter the environment variable that will be used to set credential "AZURE_CLIENT_ID" 36 | AZURE_CLIENT_ID 37 | ? How would you like to set credential "AZURE_SP_PASSWORD" 38 | environment variable 39 | ? Enter the environment variable that will be used to set credential "AZURE_SP_PASSWORD" 40 | AZURE_SP_PASSWORD 41 | ? How would you like to set credential "AZURE_SUBSCRIPTION_ID" 42 | environment variable 43 | ? Enter the environment variable that will be used to set credential "AZURE_SUBSCRIPTION_ID" 44 | AZURE_SUB_ID 45 | ? How would you like to set credential "AZURE_TENANT_ID_OR_DNS" 46 | environment variable 47 | ? Enter the environment variable that will be used to set credential "AZURE_TENANT_ID_OR_DNS" 48 | AZURE_TENANT_ID_OR_DNS 49 | 50 | porter credentials list 51 | NAME MODIFIED 52 | hello-keyvault 2 minutes ago 53 | 54 | ``` 55 | 56 | ### Install the bundle 57 | 58 | The command below installs the porter bundle. Replace the items in <> with your values. 59 | 60 | ```sh 61 | porter install -c -r ghcr.io/asalbers/hello-keyvault:v0.1.2 62 | ``` 63 | Sample output: The error at the end can be ignored for now as the keyvault successfully deploys 64 | 65 | ```sh 66 | Status: Downloaded newer image for ghcr.io/asalbers/hello-keyvault@sha256:7808c26698bdc1a61b538667177e42be1d8ef5a9223fc59e995d2d37b222f448 67 | executing install action from hello-keyvault (installation: andrew-test) 68 | Logging into Azure... 69 | Setting the Azure subscription.... 70 | Creating or using the Azure resource group.... 71 | hellokeyvault 72 | Setting random string.... 73 | uaxkx9w8 74 | Creating the KeyVault... 75 | execution completed successfully! 76 | ``` 77 | 78 | ### Uninstalling the Bundle 79 | 80 | ```sh 81 | porter uninstall -c -r ghcr.io/asalbers/hello-keyvault:v0.1.2 82 | ``` 83 | 84 | ```sh 85 | uninstalling andrew-test... 86 | executing uninstall action from hello-keyvault (installation: andrew-test) 87 | Logging into Azure... 88 | Setting the Azure subscription.... 89 | Deleting the KeyVault... 90 | execution completed successfully! 91 | ``` 92 | 93 | # Contents 94 | 95 | ## porter.yaml 96 | 97 | ### Parameters 98 | 99 | | Name | Description | Type | Default | Required | Applies to | 100 | |---------------------------|----------------------------------------------------------------------------------------------------------------|--------|---------------|----------|--------------------| 101 | | AZURE_GROUP_REGION | The azure resource group to use or create for the cluster resources. | string | eastus2 | false | All Actions | 102 | | AZURE_RESOURCE_GROUP_NAME | The azure resource group to use or create for the cluster resources. | string | hellokeyvault | false | ALL Actions | 103 | | keyvault_name | The name of the created KeyVault. | | | true | upgrade, uninstall | 104 | | random_string | The random string used to create resources. This is automatically generated and reused, but can be overridden. | | | true | upgrade, uninstall | 105 | 106 | ### Credentials 107 | 108 | | Name | Description | Required | Applies to | 109 | |---------------------------|----------------------------------------------------------------------------------|----------|-------------| 110 | | AZURE_CLIENT_ID | The client id for the service principal used to automate the bundle's actions. | true | All Actions | 111 | | AZURE_SP_PASSWORD | The service principal password that is used to log into Azure inside the bundle. | true | All Actions | 112 | | AZURE_SUBSCRIPTION_ID | The Azure subscription into which to deploy. | true | All Actions | 113 | | AZURE_TENANT_ID_OR_DNS | The tenant identity in which the service principal resides. | true | All Actions | 114 | 115 | ## helpers.sh 116 | 117 | random_string() 118 | Generates a random string to be used in the keyvault name. 119 | 120 | ## upsertGroup.sh 121 | 122 | Script that checks for an existing azure resource group and creates it if it doesn't exist. 123 | 124 | ## Dockerfile.tmpl 125 | 126 | This is a template Dockerfile for the bundle's invocation image. You can 127 | customize it to use different base images, install tools and copy configuration 128 | files. Porter will use it as a template and append lines to it for the mixin and to set 129 | the CMD appropriately for the CNAB specification. You can delete this file if you don't 130 | need it. 131 | 132 | Add the following line to **porter.yaml** to enable the Dockerfile template: 133 | 134 | ```yaml 135 | dockerfile: Dockerfile.tmpl 136 | ``` 137 | 138 | By default, the Dockerfile template is disabled and Porter automatically copies 139 | all of the files in the current directory into the bundle's invocation image. When 140 | you use a custom Dockerfile template, you must manually copy files into the bundle 141 | using COPY statements in the Dockerfile template. 142 | 143 | ## .gitignore 144 | 145 | This is a default file that we provide to help remind you which files are 146 | generated by Porter, and shouldn't be committed to source control. You can 147 | delete it if you don't need it. 148 | 149 | ## .dockerignore 150 | 151 | This is a default file that controls which files are copied into the bundle's 152 | invocation image by default. You can delete it if you don't need it. -------------------------------------------------------------------------------- /azure-keyvault/hellokeyvault-creds.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0-DRAFT+b6c701f", 3 | "name": "hello-keyvault", 4 | "created": "2021-07-23T15:12:33.8781812-05:00", 5 | "modified": "2021-07-23T15:12:33.8781812-05:00", 6 | "credentials": [ 7 | { 8 | "name": "AZURE_CLIENT_ID", 9 | "source": { 10 | "env": "AZURE_CLIENT_ID" 11 | } 12 | }, 13 | { 14 | "name": "AZURE_SP_PASSWORD", 15 | "source": { 16 | "env": "AZURE_SP_PASSWORD" 17 | } 18 | }, 19 | { 20 | "name": "AZURE_SUBSCRIPTION_ID", 21 | "source": { 22 | "env": "AZURE_SUB_ID" 23 | } 24 | }, 25 | { 26 | "name": "AZURE_TENANT_ID_OR_DNS", 27 | "source": { 28 | "env": "AZURE_TENANT_ID_OR_DNS" 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /azure-keyvault/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #set -euo pipefail 3 | 4 | random_string() { 5 | cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w ${1:-8} | head -n 1 | tr '[:upper:]' '[:lower:]' 6 | } 7 | 8 | install() { 9 | echo Hello World 10 | } 11 | 12 | upgrade() { 13 | echo World 2.0 14 | } 15 | 16 | uninstall() { 17 | echo Goodbye World 18 | } 19 | 20 | # Call the requested function and pass the arguments as-is 21 | "$@" 22 | -------------------------------------------------------------------------------- /azure-keyvault/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/hello-keyvault 3 | version: 0.1.2 4 | description: "Creates and azure keyvault using the az cli" 5 | registry: ghcr.io/getporter 6 | dockerfile: Dockerfile.tmpl 7 | 8 | mixins: 9 | - exec 10 | - az 11 | 12 | install: 13 | - az: 14 | description: "Logging into Azure..." 15 | arguments: 16 | - login 17 | suppress-output: true 18 | flags: 19 | debug: "" 20 | service-principal: 21 | username: "'{{bundle.credentials.AZURE_CLIENT_ID}}'" 22 | password: "'{{bundle.credentials.AZURE_SP_PASSWORD}}'" 23 | tenant: "'{{bundle.credentials.AZURE_TENANT_ID_OR_DNS}}'" 24 | - az: 25 | description: "Setting the Azure subscription...." 26 | suppress-output: true 27 | arguments: 28 | - "account" 29 | - "set" 30 | flags: 31 | subscription: "{{ bundle.credentials.AZURE_SUBSCRIPTION_ID}}" 32 | - exec: 33 | command: bash 34 | description: "Creating or using the Azure resource group...." 35 | arguments: 36 | - ./upsertGroup.sh 37 | - "{{bundle.parameters.AZURE_RESOURCE_GROUP_NAME}}" 38 | - "{{bundle.parameters.AZURE_GROUP_REGION}}" 39 | - exec: 40 | command: bash 41 | description: "Setting random string...." 42 | arguments: 43 | - ./helpers.sh 44 | - random_string 45 | outputs: 46 | - name: random_string 47 | regex: "(.*)" 48 | - az: 49 | description: "Creating the KeyVault..." 50 | suppress-output: true 51 | arguments: 52 | - keyvault 53 | - create 54 | flags: 55 | location: "{{bundle.parameters.AZURE_GROUP_REGION}}" 56 | name: "test-vault-{{bundle.outputs.random_string}}" 57 | resource-group: "{{bundle.parameters.AZURE_RESOURCE_GROUP_NAME}}" 58 | enable-rbac-authorization: "true" 59 | outputs: 60 | - name: "keyvault_name" 61 | jsonPath: "$.name" 62 | 63 | uninstall: 64 | - az: 65 | description: "Logging into Azure..." 66 | arguments: 67 | - login 68 | suppress-output: true 69 | flags: 70 | debug: "" 71 | service-principal: 72 | username: "'{{bundle.credentials.AZURE_CLIENT_ID}}'" 73 | password: "'{{bundle.credentials.AZURE_SP_PASSWORD}}'" 74 | tenant: "'{{bundle.credentials.AZURE_TENANT_ID_OR_DNS}}'" 75 | 76 | - az: 77 | description: "Setting the Azure subscription...." 78 | suppress-output: true 79 | arguments: 80 | - "account" 81 | - "set" 82 | flags: 83 | subscription: "{{ bundle.credentials.AZURE_SUBSCRIPTION_ID}}" 84 | 85 | - az: 86 | description: "Deleting the KeyVault..." 87 | suppress-output: true 88 | arguments: 89 | - keyvault 90 | - delete 91 | flags: 92 | name: "test-vault-{{bundle.parameters.random_string}}" 93 | resource-group: "{{bundle.parameters.AZURE_RESOURCE_GROUP_NAME}}" 94 | 95 | 96 | credentials: 97 | - name: AZURE_CLIENT_ID 98 | description: "The client id for the service principal used to automate the bundle's actions." 99 | env: AZURE_CLIENT_ID 100 | - name: AZURE_TENANT_ID_OR_DNS 101 | description: "The tenant identity in which the service principal resides." 102 | env: AZURE_TENANT_ID_OR_DNS 103 | - name: AZURE_SP_PASSWORD 104 | description: "The service principal password that is used to log into Azure inside the bundle." 105 | env: AZURE_SP_PASSWORD 106 | - name: AZURE_SUBSCRIPTION_ID 107 | description: "The Azure subscription into which to deploy." 108 | env: AZURE_SUB_ID 109 | 110 | parameters: 111 | - name: AZURE_RESOURCE_GROUP_NAME 112 | description: "The azure resource group to use or create for the cluster resources." 113 | type: string 114 | default: hellokeyvault 115 | - name: AZURE_GROUP_REGION 116 | description: "The azure resource group to use or create for the cluster resources." 117 | type: string 118 | default: eastus2 119 | - name: random_string 120 | description: "The random string used to create resources. This is automatically generated and reused, but can be overridden." 121 | type: string 122 | applyTo: 123 | - "upgrade" 124 | - "uninstall" 125 | source: 126 | output: random_string 127 | - name: keyvault_name 128 | description: "The name of the created KeyVault." 129 | type: string 130 | source: 131 | output: keyvault_name 132 | applyTo: 133 | - "upgrade" 134 | - "uninstall" 135 | - name: AZURE_GROUP_REGION 136 | description: "The azure resource group to use or create for the cluster resources." 137 | type: string 138 | default: eastus2 139 | 140 | outputs: 141 | - name: random_string 142 | description: "The random string to use in generating Azure resources." 143 | type: string 144 | applyTo: 145 | - "install" 146 | - name: keyvault_name 147 | description: "The keyvault name that is created." 148 | type: string 149 | applyTo: 150 | - "install" 151 | 152 | -------------------------------------------------------------------------------- /azure-keyvault/upsertGroup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | groupName=$1 3 | groupRegion=$2 4 | echo "$groupName" 5 | group=$(az group list --query "[?name == '$groupName'].name" -o tsv) 6 | if [ "$group" != "$groupName" ]; then 7 | az group create -n $groupName -l $groupRegion 8 | fi -------------------------------------------------------------------------------- /azure-terraform/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl 5 | -------------------------------------------------------------------------------- /azure-terraform/.gitignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .vscode/ 3 | .cnab 4 | -------------------------------------------------------------------------------- /azure-terraform/README.md: -------------------------------------------------------------------------------- 1 | # Using Porter with Azure and Terraform 2 | 3 | This bundle provides an example of how you can use Porter to build Terraform-based bundles. The example provided here will create Azure CosmosDB and Azure EventHubs objects using Terraform configurations and the [porter-terraform](https://github.com/getporter/terraform-mixin) mixin. This sample also shows how the Terraform mixin can be used with other mixins, in this case the ARM mixin. The ARM mixin is first used to create an Azure storage account that will be used to configure the Terraform `azurerm` backend. It is possible to build bundles using just the [porter-terraform](https://github.com/getporter/terraform-mixin) mixin, but this example shows you how to use outputs between steps as well. 4 | 5 | ## Setup 6 | 7 | This bundle will create resources in Azure. In order to do this, you'll first need to [create an Azure account](https://azure.microsoft.com/en-us/free/) if you don't already have one. 8 | 9 | The bundle will use an Azure [Service Principal](https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals) in order to authenticate with Azure. Once you have an account, create a Service Principal for use with the bundle. You can do this via the Azure portal, or via the Azure CLI: 10 | 11 | 1. Create a service principal with the Azure CLI: 12 | ```console 13 | az ad sp create-for-rbac --name porterraform -o table 14 | ``` 15 | 1. Save the values from the command output in environment variables: 16 | 17 | **Bash** 18 | ```console 19 | export AZURE_TENANT_ID= 20 | export AZURE_CLIENT_ID= 21 | export AZURE_CLIENT_SECRET= 22 | ``` 23 | 24 | **PowerShell** 25 | ```console 26 | $env:AZURE_TENANT_ID = "" 27 | $env:AZURE_CLIENT_ID = "" 28 | $env:AZURE_CLIENT_SECRET = "" 29 | ``` 30 | 31 | You will also need to have [Porter](https://porter.sh/install/) and [Docker](https://docs.docker.com/v17.12/install/) installed before you proceed. 32 | 33 | ## Overview 34 | 35 | If you examine the contents of this example, you will see a `porter.yaml` file and a `terraform` directory. The `porter.yaml` is the bundle definition that will be used to build and run the bundle. Please see the [porter.yaml](porter.yaml) for the contents of the file. In this file, you will find a few credential definitions: 36 | 37 | ```yaml 38 | credentials: 39 | - name: subscription_id 40 | env: AZURE_SUBSCRIPTION_ID 41 | 42 | - name: tenant_id 43 | env: AZURE_TENANT_ID 44 | 45 | - name: client_id 46 | env: AZURE_CLIENT_ID 47 | 48 | - name: client_secret 49 | env: AZURE_CLIENT_SECRET 50 | ``` 51 | 52 | These credentials will correspond to the Azure Service Principal you created above. You'll also notice some parameter definitions: 53 | 54 | ```yaml 55 | parameters: 56 | - name: location 57 | type: string 58 | default: "EastUS" 59 | 60 | - name: resource_group_name 61 | type: string 62 | default: "porter-terraform" 63 | 64 | - name: storage_account_name 65 | type: string 66 | default: "porterstorage" 67 | 68 | - name: storage_container_name 69 | type: string 70 | default: "portertf" 71 | 72 | - name: storage_rg 73 | type: string 74 | default: "porter-storage" 75 | 76 | - name: database-name 77 | type: string 78 | default: "porter-terraform" 79 | ``` 80 | 81 | These represent the values that can be provided at runtime when installing the bundle. 82 | 83 | Finally, please note how the `porter-terraform` mixin is used: 84 | 85 | ```yaml 86 | - terraform: 87 | description: "Create Azure CosmosDB and Event Hubs" 88 | backendConfig: 89 | key: ${ bundle.name }}.tfstate 90 | storage_account_name: ${ bundle.parameters.storage_account_name } 91 | container_name: ${ bundle.parameters.storage_container_name } 92 | access_key: ${ bundle.outputs.storage_account_key } 93 | vars: 94 | subscription_id: ${bundle.credentials.subscription_id} 95 | tenant_id: ${bundle.credentials.tenant_id} 96 | client_id: ${bundle.credentials.client_id} 97 | client_secret: ${bundle.credentials.client_secret} 98 | database_name: ${bundle.parameters.database-name} 99 | resource_group_name: ${bundle.parameters.resource_group_name} 100 | resource_group_location: ${bundle.parameters.location} 101 | outputs: 102 | - name: cosmos-db-uri 103 | - name: eventhubs_connection_string 104 | ``` 105 | 106 | This mixin step uses both parameters and credentials, defined above, and declares two output values. The source of these output files is defined in the Terraform configuration. 107 | 108 | The `terraform` directory contains the Terraform configuration files that will be used by the `porter-terraform` mixin: 109 | 110 | ```bash 111 | ls -l terraform/ 112 | total 40 113 | -rw-r--r-- 1 jeremyrickard staff 1023 Jul 2 08:32 cosmos-db.tf 114 | -rw-r--r-- 1 jeremyrickard staff 622 Jul 2 08:30 eventhubs.tf 115 | -rw-r--r-- 1 jeremyrickard staff 290 Jul 3 10:43 main.tf 116 | -rw-r--r-- 1 jeremyrickard staff 286 Jul 3 17:09 outputs.tf 117 | -rw-r--r-- 1 jeremyrickard staff 325 Jul 2 08:11 variables.tf 118 | ``` 119 | 120 | The `main.tf` file configures the `azurerm` provider, while the `outputs.tf` defines the outputs we will capture from the Terraform step. `cosmos-db.tf` and `eventhubs.tf` contain the declarations for the infrastructure we will create. Finally, `variables.tf` defines a set of variables used throughout the files. These correspond to the parameters in our `porter.yaml` above. 121 | 122 | ## Building The Bundle 123 | 124 | In order to use this bundle, you'll first need to build it. This is done with the `porter` command line tool. Ensure that your working directory is set to this example directory before proceeding, then run the following command: 125 | 126 | ``` 127 | porter build 128 | ``` 129 | 130 | Once this command has finished, you will see a `.cnab` directory created in the working directory. This directory contains the `Dockerfile` for the bundle's installer image generated by Porter and the `porter-terraform` mixin: 131 | 132 | ```bash 133 | $ more .cnab/Dockerfile 134 | FROM debian:stable-slim 135 | 136 | ARG BUNDLE_DIR 137 | 138 | RUN apt-get update && apt-get install -y ca-certificates 139 | 140 | ENV TERRAFORM_VERSION=1.0.4 141 | RUN apt-get update && apt-get install -y wget unzip && \ 142 | wget https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \ 143 | unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip -d /usr/bin && \ 144 | rm terraform_${TERRAFORM_VERSION}_linux_amd64.zip 145 | COPY . ${BUNDLE_DIR} 146 | RUN cd /cnab/app/terraform && terraform init -backend=false 147 | 148 | # exec mixin has no buildtime dependencies 149 | 150 | COPY . ${BUNDLE_DIR} 151 | RUN rm ${BUNDLE_DIR}/porter.yaml 152 | RUN rm -fr ${BUNDLE_DIR}/.cnab 153 | COPY .cnab /cnab 154 | WORKDIR ${BUNDLE_DIR} 155 | CMD ["/cnab/app/run"] 156 | ``` 157 | 158 | The Dockerfile contains the necessary instructions to install Terraform within our bundle's invocation image. 159 | 160 | ## Generate a Credential Set 161 | 162 | Before you can install the bundle, you'll need to generate a credential set. This credential set will map your local service principal environment variables into the destinations defined in the `porter.yaml`. You can generate the credential set by running `porter credentials generate`. This will prompt you for the source for each credential defined in the bundle: 163 | 164 | ```bash 165 | $ porter credentials generate 166 | Generating new credential azure-terraform from bundle azure-terraform 167 | ==> 4 credentials required for bundle azure-terraform 168 | ? How would you like to set credential "client_id" [Use arrows to move, space to select, type to filter] 169 | specific value 170 | > environment variable 171 | file path 172 | shell command 173 | ``` 174 | 175 | For each of the credentials, provide the corresponding environment variable that you set above. For example, for `client_id`, select `environment variable` and provide the value `AZURE_CLIENT_ID`. 176 | 177 | ```bash 178 | $ porter credentials generate 179 | Generating new credential azure-terraform from bundle azure-terraform 180 | ==> 4 credentials required for bundle azure-terraform 181 | ? How would you like to set credential "client_id" environment variable 182 | ? Enter the environment variable that will be used to set credential "client_id" AZURE_CLIENT_ID 183 | ? How would you like to set credential "client_secret" environment variable 184 | ? Enter the environment variable that will be used to set credential "client_secret" AZURE_CLIENT_SECRET 185 | ? How would you like to set credential "subscription_id" environment variable 186 | ? Enter the environment variable that will be used to set credential "subscription_id" AZURE_SUBSCRIPTION_ID 187 | ? How would you like to set credential "tenant_id" environment variable 188 | ? Enter the environment variable that will be used to set credential "tenant_id" AZURE_TENANT_ID 189 | Saving credential to /Users/jeremyrickard/.porter/credentials/azure-terraform.yaml 190 | ``` 191 | 192 | ## (OPTIONAL) Generate a Parameter Set 193 | 194 | Similar to generating a credential set, you may generate a parameter set if you wish to supply custom values for some/all of the parameters. However, all of the required parameters have default values declared in the Porter manifest, so this step is optional. 195 | 196 | To generate a parameter set, run `porter parameters generate`. The parameter set can then be supplied on install via `-p azure-terraform`. 197 | 198 | ## Installing the Bundle 199 | 200 | Once you have built the bundle and generated a credential set, you're ready to install the bundle! To do that, you'll use the `porter install` command: 201 | 202 | ```bash 203 | $ porter install -c azure-terraform 204 | installing azure-terraform... 205 | executing install action from azure-terraform (installation: azure-terraform) 206 | Create an Azure Storage Account 207 | Starting deployment operations... 208 | Finished deployment operations... 209 | Create Azure CosmosDB and Event Hubs 210 | Initializing Terraform... 211 | /usr/bin/terraform terraform init -backend=true -backend-config=access_key=******* -backend-config=container_name=portertf -backend-config=key=azure-terraform.tfstate -backend-config=storage_account_name=porterstorage -reconfigure 212 | 213 | Initializing the backend... 214 | 215 | Successfully configured the backend "azurerm"! Terraform will automatically 216 | use this backend unless the backend configuration changes. 217 | 218 | Initializing provider plugins... 219 | 220 | Terraform has been successfully initialized! 221 | 222 | You may now begin working with Terraform. Try running "terraform plan" to see 223 | any changes that are required for your infrastructure. All Terraform commands 224 | should now work. 225 | 226 | If you ever set or change modules or backend configuration for Terraform, 227 | rerun this command to reinitialize your working directory. If you forget, other 228 | commands will detect it and remind you to do so if necessary. 229 | Acquiring state lock. This may take a few moments... 230 | azurerm_resource_group.rg: Creating... 231 | 232 | 233 | 234 | Apply complete! Resources: 5 added, 0 changed, 0 destroyed. 235 | 236 | Outputs: 237 | 238 | cosmos-db-uri = 239 | eventhubs_connection_string = 240 | eventhubs_topic = "porterform-eventhub" 241 | execution completed successfully! 242 | ``` 243 | 244 | Installing the bundle will take some amount of time, especially the Azure deployments of the CosmosDB and EventHubs resources. 245 | 246 | Assuming all goes well, you should see the relevant resource outputs printed and `execution completed successfully!` 247 | 248 | ## Inspecting Outputs 249 | 250 | Now that the bundle has been installed, outputs can be inspected in the following ways. 251 | 252 | First, we can get an overview of this installation via `porter installation show`: 253 | 254 | ``` 255 | $ porter installation show 256 | Name: azure-terraform 257 | Created: 4 minutes ago 258 | Modified: 4 minutes ago 259 | 260 | Outputs: 261 | ------------------------------------------------------------------------------------------------------------- 262 | Name Type Value 263 | ------------------------------------------------------------------------------------------------------------- 264 | cosmos-db-uri string mongodb://porterform-cosmos-db:6At4uDIX0o9k1eMot8kRgjldfe... 265 | eventhubs_connection_string string Endpoint=sb://porterform-eventhub-ns.servicebus.windows.n... 266 | io.cnab.outputs.invocationImageLogs string executing install action from 267 | azure-terraform (installati... 268 | storage_account_key string p6UVIHV7kF4n+/AzzUh7Usd8DzRPJJa5H9KRwrLau0mjcG6QRw298dUxY... 269 | 270 | History: 271 | ------------------------------------------------------------------------------ 272 | Run ID Action Timestamp Status Has Logs 273 | ------------------------------------------------------------------------------ 274 | 01FDT6GPBXZJD1DEY5ZW6CB9Y8 install 4 minutes ago succeeded true 275 | ``` 276 | 277 | We can also show a specific output via `porter installation output show [NAME]`: 278 | 279 | ``` 280 | $ porter installation output show cosmos-db-uri 281 | mongodb://porterform-cosmos-db:6At4uDIX0o9k1eMot8kRgjldfeRBDqONIK7bXD03u3GXi1S1YkX2Rfmxj8hqunbxzlU9KLqtZLe3LM7g4GzPAg==@porterform-cosmos-db.mongo.cosmos.azure.com:10255/?ssl=true&replicaSet=globaldb&retrywrites=false&maxIdleTimeMS=120000&appName=@porterform-cosmos-db@ 282 | ``` 283 | 284 | ## Uninstalling the Bundle 285 | 286 | When you're ready to uninstall the bundle, simply run the `porter uninstall` command: 287 | 288 | ```bash 289 | $ porter uninstall -c azure-terraform 290 | uninstalling azure-terraform... 291 | executing uninstall action from azure-terraform (installation: azure-terraform) 292 | Remove Azure CosmosDB and Event Hubs 293 | Initializing Terraform... 294 | /usr/bin/terraform terraform init -backend=true -backend-config=access_key=******* -backend-config=container_name=portertf -backend-config=key=azure-terraform.tfstate -backend-config=storage_account_name=porterstorage -reconfigure 295 | 296 | Initializing the backend... 297 | 298 | Successfully configured the backend "azurerm"! Terraform will automatically 299 | use this backend unless the backend configuration changes. 300 | 301 | Initializing provider plugins... 302 | 303 | Terraform has been successfully initialized! 304 | 305 | You may now begin working with Terraform. Try running "terraform plan" to see 306 | any changes that are required for your infrastructure. All Terraform commands 307 | should now work. 308 | 309 | If you ever set or change modules or backend configuration for Terraform, 310 | rerun this command to reinitialize your working directory. If you forget, other 311 | commands will detect it and remind you to do so if necessary. 312 | Acquiring state lock. This may take a few moments... 313 | azurerm_resource_group.rg: Refreshing state... 314 | 315 | 316 | 317 | azurerm_resource_group.rg: Destruction complete after 33s 318 | 319 | Destroy complete! Resources: 5 destroyed. 320 | Remove the Azure Storage Account 321 | execution completed successfully! 322 | ``` 323 | 324 | This will take a number of minutes to finish, but when complete the resources will be removed from your account. 325 | -------------------------------------------------------------------------------- /azure-terraform/arm/storage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "location": { 6 | "type": "string" 7 | }, 8 | "storageAccountName": { 9 | "type": "string" 10 | }, 11 | "storageContainerName": { 12 | "type": "string" 13 | } 14 | }, 15 | "resources": [ 16 | { 17 | "name": "[parameters('storageAccountName')]", 18 | "type": "Microsoft.Storage/storageAccounts", 19 | "apiVersion": "2018-07-01", 20 | "sku": { 21 | "name": "Standard_GRS" 22 | }, 23 | "kind": "StorageV2", 24 | "location": "[parameters('location')]", 25 | "identity": { 26 | "type": "SystemAssigned" 27 | }, 28 | "properties": { 29 | "encryption": { 30 | "services": { 31 | "blob": { 32 | "enabled": true 33 | } 34 | }, 35 | "keySource": "Microsoft.Storage" 36 | }, 37 | "supportsHttpsTrafficOnly": true 38 | }, 39 | "resources": [ 40 | { 41 | "name": "[concat(parameters('storageAccountName'), '/default/', parameters('storageContainerName'))]", 42 | "dependsOn": [ 43 | "[parameters('storageAccountName')]" 44 | ], 45 | "type": "Microsoft.Storage/storageAccounts/blobServices/containers", 46 | "apiVersion": "2018-07-01", 47 | "properties": { 48 | "publicAccess": "None" 49 | } 50 | } 51 | ] 52 | } 53 | ], 54 | "outputs": { 55 | "storage_account_key": { 56 | "type": "string", 57 | "value": "[first(listKeys(parameters('storageAccountName'), '2018-02-01').keys).value]" 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /azure-terraform/porter.yaml: -------------------------------------------------------------------------------- 1 | ## This section defines the metadata that defines the bundle. This includes the name of the generated 2 | ## invocation image as well as the tag that will be applied to the bundle when it is published. 3 | 4 | schemaVersion: 1.0.0 5 | name: examples/azure-terraform 6 | version: 2.1.0 7 | description: "An example Porter Bundle using Terraform and Azure" 8 | registry: ghcr.io/getporter 9 | 10 | ## This section defines which Mixins will be used by the bundle. 11 | mixins: 12 | - arm 13 | - exec 14 | - terraform: 15 | clientVersion: 1.2.9 16 | 17 | ## This section defines what credentials are used for the bundle. In this case, we are operating 18 | ## against Azure, so we need some Azure Service Principal information. 19 | credentials: 20 | - name: subscription_id 21 | env: AZURE_SUBSCRIPTION_ID 22 | 23 | - name: tenant_id 24 | env: AZURE_TENANT_ID 25 | 26 | - name: client_id 27 | env: AZURE_CLIENT_ID 28 | 29 | - name: client_secret 30 | env: AZURE_CLIENT_SECRET 31 | 32 | ## This section defines what parameters are used by the bundle. These parameters are used by various 33 | ## steps within the bundle 34 | parameters: 35 | - name: location 36 | type: string 37 | default: "EastUS" 38 | 39 | - name: resource_group_name 40 | type: string 41 | default: "porter-terraform" 42 | 43 | - name: storage_account_name 44 | type: string 45 | default: "porterstorage" 46 | 47 | - name: storage_container_name 48 | type: string 49 | default: "portertf" 50 | 51 | - name: storage_rg 52 | type: string 53 | default: "porter-storage" 54 | 55 | - name: database-name 56 | type: string 57 | default: "porter-terraform" 58 | 59 | ## Here we designate outputs at the top level that we wish 60 | ## to see after an action has completed (via `porter installation outputs list`) 61 | ## and/or utilize in a subsequent action. 62 | outputs: 63 | - name: storage_account_key 64 | type: string 65 | sensitive: true 66 | applyTo: 67 | - install 68 | 69 | - name: cosmos-db-uri 70 | type: string 71 | applyTo: 72 | - install 73 | - upgrade 74 | 75 | - name: eventhubs_connection_string 76 | type: string 77 | sensitive: true 78 | applyTo: 79 | - install 80 | - upgrade 81 | 82 | install: 83 | - arm: 84 | description: "Create an Azure Storage Account" 85 | type: arm 86 | template: "arm/storage.json" 87 | name: ${ bundle.parameters.storage_account_name } 88 | resourceGroup: ${ bundle.parameters.storage_rg } 89 | parameters: 90 | location: ${ bundle.parameters.location } 91 | storageAccountName: ${ bundle.parameters.storage_account_name } 92 | storageContainerName: ${ bundle.parameters.storage_container_name } 93 | outputs: 94 | - name: storage_account_key 95 | key: storage_account_key 96 | 97 | - terraform: 98 | description: "Create Azure CosmosDB and Event Hubs" 99 | backendConfig: 100 | key: ${ bundle.name }}.tfstate 101 | storage_account_name: ${ bundle.parameters.storage_account_name } 102 | container_name: ${ bundle.parameters.storage_container_name } 103 | access_key: ${ bundle.outputs.storage_account_key } 104 | vars: 105 | subscription_id: ${bundle.credentials.subscription_id} 106 | tenant_id: ${bundle.credentials.tenant_id} 107 | client_id: ${bundle.credentials.client_id} 108 | client_secret: ${bundle.credentials.client_secret} 109 | database_name: ${bundle.parameters.database-name} 110 | resource_group_name: ${bundle.parameters.resource_group_name} 111 | resource_group_location: ${bundle.parameters.location} 112 | outputs: 113 | - name: cosmos-db-uri 114 | - name: eventhubs_connection_string 115 | 116 | upgrade: 117 | - terraform: 118 | description: "Update Azure CosmosDB and Event Hubs" 119 | vars: 120 | subscription_id: ${bundle.credentials.subscription_id} 121 | tenant_id: ${bundle.credentials.tenant_id} 122 | client_id: ${bundle.credentials.client_id} 123 | client_secret: ${bundle.credentials.client_secret} 124 | database_name: ${bundle.parameters.database-name} 125 | resource_group_name: ${bundle.parameters.resource_group_name} 126 | resource_group_location: ${bundle.parameters.location} 127 | backendConfig: 128 | key: ${ bundle.name }}.tfstate 129 | storage_account_name: ${ bundle.parameters.storage_account_name } 130 | container_name: ${ bundle.parameters.storage_container_name } 131 | access_key: ${ bundle.outputs.storage_account_key } 132 | outputs: 133 | - name: cosmos-db-uri 134 | - name: eventhubs_connection_string 135 | 136 | uninstall: 137 | - terraform: 138 | description: "Remove Azure CosmosDB and Event Hubs" 139 | backendConfig: 140 | key: ${ bundle.name }}.tfstate 141 | storage_account_name: ${ bundle.parameters.storage_account_name } 142 | container_name: ${ bundle.parameters.storage_container_name } 143 | access_key: ${ bundle.outputs.storage_account_key } 144 | vars: 145 | subscription_id: ${bundle.credentials.subscription_id} 146 | tenant_id: ${bundle.credentials.tenant_id} 147 | client_id: ${bundle.credentials.client_id} 148 | client_secret: ${bundle.credentials.client_secret} 149 | database_name: ${bundle.parameters.database-name} 150 | resource_group_name: ${bundle.parameters.resource_group_name} 151 | resource_group_location: ${bundle.parameters.location} 152 | 153 | - arm: 154 | description: "Remove the Azure Storage Account" 155 | type: arm 156 | template: "arm/storage.json" 157 | name: ${ bundle.parameters.storage_account_name } 158 | resourceGroup: ${ bundle.parameters.storage_rg } 159 | parameters: 160 | location: ${ bundle.parameters.location } 161 | storageAccountName: ${ bundle.parameters.storage_account_name } 162 | storageContainerName: ${ bundle.parameters.storage_container_name } 163 | -------------------------------------------------------------------------------- /azure-terraform/terraform/cosmos-db.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_resource_group" "rg" { 2 | name = var.resource_group_name 3 | location = var.resource_group_location 4 | } 5 | 6 | resource "azurerm_cosmosdb_account" "db" { 7 | name = "porterform-cosmos-db" 8 | location = azurerm_resource_group.rg.location 9 | resource_group_name = azurerm_resource_group.rg.name 10 | offer_type = "Standard" 11 | kind = "MongoDB" 12 | 13 | enable_automatic_failover = false 14 | 15 | consistency_policy { 16 | consistency_level = "BoundedStaleness" 17 | max_interval_in_seconds = 301 18 | max_staleness_prefix = 100001 19 | } 20 | 21 | geo_location { 22 | location = var.failover_location 23 | failover_priority = 1 24 | } 25 | 26 | geo_location { 27 | location = azurerm_resource_group.rg.location 28 | failover_priority = 0 29 | } 30 | } 31 | 32 | resource "azurerm_cosmosdb_mongo_database" "db" { 33 | name = var.database_name 34 | resource_group_name = azurerm_cosmosdb_account.db.resource_group_name 35 | account_name = azurerm_cosmosdb_account.db.name 36 | } -------------------------------------------------------------------------------- /azure-terraform/terraform/eventhubs.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_eventhub_namespace" "hubs" { 2 | name = "porterform-eventhub-ns" 3 | location = azurerm_resource_group.rg.location 4 | resource_group_name = azurerm_resource_group.rg.name 5 | sku = "Standard" 6 | capacity = 1 7 | 8 | tags = { 9 | environment = "Production" 10 | } 11 | } 12 | 13 | resource "azurerm_eventhub" "hubs" { 14 | name = "porterform-eventhub" 15 | namespace_name = azurerm_eventhub_namespace.hubs.name 16 | resource_group_name = azurerm_resource_group.rg.name 17 | partition_count = 2 18 | message_retention = 1 19 | } -------------------------------------------------------------------------------- /azure-terraform/terraform/main.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | features {} 3 | subscription_id = var.subscription_id 4 | client_id = var.client_id 5 | client_secret = var.client_secret 6 | tenant_id = var.tenant_id 7 | } 8 | 9 | terraform { 10 | required_version = "1.2.9" 11 | required_providers { 12 | azurerm = { 13 | source = "hashicorp/azurerm" 14 | version = "=3.22.0" 15 | } 16 | } 17 | backend "azurerm" {} 18 | } -------------------------------------------------------------------------------- /azure-terraform/terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | output "cosmos-db-uri" { 2 | value = azurerm_cosmosdb_account.db.connection_strings[0] 3 | sensitive = true 4 | } 5 | 6 | output "eventhubs_connection_string" { 7 | value = azurerm_eventhub_namespace.hubs.default_primary_connection_string 8 | sensitive = true 9 | } 10 | 11 | output "eventhubs_topic" { 12 | value = azurerm_eventhub.hubs.name 13 | } -------------------------------------------------------------------------------- /azure-terraform/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "client_id" {} 2 | variable "client_secret" {} 3 | variable "tenant_id" {} 4 | variable "subscription_id" {} 5 | 6 | 7 | variable "database_name" {} 8 | 9 | variable "resource_group_name" { 10 | default = "azure-porter-tf" 11 | } 12 | 13 | variable "resource_group_location" { 14 | default = "East US" 15 | } 16 | 17 | variable "failover_location" { 18 | default = "West US" 19 | } -------------------------------------------------------------------------------- /azure-wordpress/.gitignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .cnab -------------------------------------------------------------------------------- /azure-wordpress/README.md: -------------------------------------------------------------------------------- 1 | # Azure MySQL WordPress Example 2 | 3 | 1. porter build 4 | 1. porter credentials generate 5 | You will need an Azure service principal. Put the service principal credentials in environment variables. 6 | 1. porter install --cred azure-wordpress --param-file params.ini -------------------------------------------------------------------------------- /azure-wordpress/arm/mysql.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "administratorLogin": { 6 | "type": "string" 7 | }, 8 | "administratorLoginPassword": { 9 | "type": "securestring" 10 | }, 11 | "location": { 12 | "type": "string" 13 | }, 14 | "serverName": { 15 | "type": "string" 16 | }, 17 | "databaseName": { 18 | "type": "string" 19 | }, 20 | "version": { 21 | "type": "string" 22 | }, 23 | "sslEnforcement": { 24 | "type": "string" 25 | } 26 | }, 27 | "resources": [ 28 | { 29 | "apiVersion": "2017-12-01-preview", 30 | "kind": "", 31 | "location": "[parameters('location')]", 32 | "name": "[parameters('serverName')]", 33 | "properties": { 34 | "version": "[parameters('version')]", 35 | "administratorLogin": "[parameters('administratorLogin')]", 36 | "administratorLoginPassword": "[parameters('administratorLoginPassword')]", 37 | "sslEnforcement": "[parameters('sslEnforcement')]", 38 | "storageProfile": { 39 | "storageMB": "102400", 40 | "backupRetentionDays": 7, 41 | "geoRedundantBackup": "Disabled" 42 | } 43 | }, 44 | "sku": { 45 | "name": "GP_Gen5_4", 46 | "tier": "GeneralPurpose", 47 | "capacity": 4, 48 | "size": 102400, 49 | "family": "Gen5" 50 | }, 51 | "type": "Microsoft.DBforMySQL/servers", 52 | "resources": [ 53 | { 54 | "apiVersion": "2017-12-01-preview", 55 | "name": "[parameters('databaseName')]", 56 | "type": "databases", 57 | "location": "[parameters('location')]", 58 | "dependsOn": [ 59 | "[concat('Microsoft.DBforMySQL/servers/', parameters('serverName'))]" 60 | ], 61 | "properties": {} 62 | } 63 | ] 64 | } 65 | ], 66 | "outputs": { 67 | "MYSQL_HOST": { 68 | "type": "string", 69 | "value": "[reference(parameters('serverName')).fullyQualifiedDomainName]" 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /azure-wordpress/params.ini: -------------------------------------------------------------------------------- 1 | mysql_password=iloveporter-127c6!J9$ 2 | server_name=iloveporter -------------------------------------------------------------------------------- /azure-wordpress/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/azure-wordpress 3 | version: 0.2.0 4 | registry: ghcr.io/getporter 5 | 6 | 7 | mixins: 8 | - arm 9 | - helm3: 10 | repositories: 11 | bitnami: 12 | url: "https://charts.bitnami.com/bitnami" 13 | credentials: 14 | - name: SUBSCRIPTION_ID 15 | env: AZURE_SUBSCRIPTION_ID 16 | - name: CLIENT_ID 17 | env: AZURE_CLIENT_ID 18 | - name: TENANT_ID 19 | env: AZURE_TENANT_ID 20 | - name: CLIENT_SECRET 21 | env: AZURE_CLIENT_SECRET 22 | - name: kubeconfig 23 | path: /home/nonroot/.kube/config 24 | 25 | parameters: 26 | - name: mysql_user 27 | type: string 28 | default: azureuser 29 | 30 | - name: mysql_password 31 | type: string 32 | sensitive: true 33 | 34 | - name: database_name 35 | type: string 36 | default: "wordpress" 37 | 38 | - name: server_name 39 | type: string 40 | 41 | - name: resource_group 42 | type: string 43 | default: "porter-test" 44 | 45 | install: 46 | - arm: 47 | description: "Create Azure MySQL" 48 | type: arm 49 | template: "arm/mysql.json" 50 | name: mysql-azure-porter-demo-wordpress 51 | resourceGroup: "{{ bundle.parameters.resource_group }}" 52 | parameters: 53 | administratorLogin: "{{ bundle.parameters.mysql_user }}" 54 | administratorLoginPassword: "{{ bundle.parameters.mysql_password }}" 55 | location: "eastus" 56 | serverName: "{{ bundle.parameters.server_name }}" 57 | version: "5.7" 58 | sslEnforcement: "Disabled" 59 | databaseName: "{{ bundle.parameters.database_name }}" 60 | outputs: 61 | - name: "MYSQL_HOST" 62 | key: "MYSQL_HOST" 63 | 64 | - helm3: 65 | description: "Helm Install Wordpress" 66 | name: porter-ci-wordpress 67 | chart: bitnami/wordpress 68 | version: "9.9.3" 69 | replace: true 70 | set: 71 | mariadb.enabled: "false" 72 | externalDatabase.port: 3306 73 | readinessProbe.initialDelaySeconds: 120 74 | externalDatabase.host: "{{ bundle.outputs.MYSQL_HOST }}" 75 | externalDatabase.user: "{{ bundle.parameters.mysql_user }}" 76 | externalDatabase.password: "{{ bundle.parameters.mysql_password }}" 77 | externalDatabase.database: "{{ bundle.parameters.database_name }}" 78 | 79 | uninstall: 80 | # TODO: enable once the porter-arm mixin implements uninstall 81 | # see https://github.com/getporter/arm-mixin/issues/7 82 | # - arm: 83 | # description: "Uninstall Mysql" 84 | # name: mysql-azure-porter-demo-wordpress 85 | - helm3: 86 | description: "Helm Uninstall Wordpress" 87 | purge: true 88 | releases: 89 | - "porter-ci-wordpress" 90 | 91 | 92 | -------------------------------------------------------------------------------- /credentials-tutorial/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl 5 | -------------------------------------------------------------------------------- /credentials-tutorial/.gitignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .cnab/ 3 | -------------------------------------------------------------------------------- /credentials-tutorial/Dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | FROM debian:stable-slim 2 | 3 | ARG BUNDLE_DIR 4 | 5 | RUN apt-get update && apt-get install -y ca-certificates curl 6 | 7 | # This is a template Dockerfile for the bundle's invocation image 8 | # You can customize it to use different base images, install tools and copy configuration files. 9 | # 10 | # Porter will use it as a template and append lines to it for the mixins 11 | # and to set the CMD appropriately for the CNAB specification. 12 | # 13 | # Add the following line to porter.yaml to instruct Porter to use this template 14 | # dockerfile: Dockerfile.tmpl 15 | 16 | # You can control where the mixin's Dockerfile lines are inserted into this file by moving "# PORTER_MIXINS" line 17 | # another location in this file. If you remove that line, the mixins generated content is appended to this file. 18 | # PORTER_MIXINS 19 | 20 | # Use the BUNDLE_DIR build argument to copy files into the bundle 21 | COPY . ${BUNDLE_DIR} 22 | -------------------------------------------------------------------------------- /credentials-tutorial/README.md: -------------------------------------------------------------------------------- 1 | # Credentials Tutorial Bundle 2 | 3 | This bundle demonstrates how to define and use a credential in a bundle and is used in the [Credentials QuickStart]. 4 | 5 | [Credentials QuickStart]: https://porter.sh/quickstart/credentials/ 6 | -------------------------------------------------------------------------------- /credentials-tutorial/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | getUser() { 5 | url="https://api.github.com/user" 6 | if [[ "$GITHUB_USER" != "" ]]; then 7 | url="https://api.github.com/users/$GITHUB_USER" 8 | fi 9 | curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" $url 10 | } 11 | 12 | # Call the requested function and pass the arguments as-is 13 | "$@" 14 | -------------------------------------------------------------------------------- /credentials-tutorial/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/credentials-tutorial 3 | version: 0.3.0 4 | description: "An example Porter bundle with credentials. Uses your GitHub token to retrieve your public user profile from GitHub." 5 | registry: ghcr.io/getporter 6 | dockerfile: Dockerfile.tmpl 7 | 8 | mixins: 9 | - exec 10 | 11 | credentials: 12 | - name: github-token 13 | description: A GitHub Personal Access Token. Generate one at https://github.com/settings/tokens. No scopes are required. 14 | env: GITHUB_TOKEN 15 | applyTo: 16 | - install 17 | - upgrade 18 | 19 | parameters: 20 | - name: user 21 | description: A GitHub username. Defaults to the current user. 22 | env: GITHUB_USER 23 | applyTo: 24 | - install 25 | - upgrade 26 | default: '' 27 | 28 | install: 29 | - exec: 30 | description: "Retrieve a user profile from GitHub" 31 | command: ./helpers.sh 32 | arguments: 33 | - getUser 34 | 35 | upgrade: 36 | - exec: 37 | description: "Retrieve a user profile from GitHub" 38 | command: ./helpers.sh 39 | arguments: 40 | - getUser 41 | 42 | uninstall: 43 | - exec: 44 | description: "Uninstall credentials tutorial" 45 | command: echo 46 | arguments: 47 | - "Nothing to uninstall. Bye!" 48 | -------------------------------------------------------------------------------- /docker/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl 5 | -------------------------------------------------------------------------------- /docker/.gitignore: -------------------------------------------------------------------------------- 1 | .cnab/ 2 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # Example: Docker 2 | 3 | Try out this example at https://porter.sh/examples/docker/. -------------------------------------------------------------------------------- /docker/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/whalesay 3 | version: 0.2.1 4 | description: "An example bundle that uses docker through the magic of whalespeak" 5 | registry: ghcr.io/getporter 6 | 7 | required: 8 | - docker 9 | 10 | parameters: 11 | - name: msg 12 | description: a message for the whales to speak 13 | type: string 14 | default: "whale hello there!" 15 | applyTo: 16 | - say 17 | 18 | mixins: 19 | - docker 20 | 21 | install: 22 | - docker: 23 | run: 24 | image: "ghcr.io/getporter/examples/images/whalesay:latest" 25 | rm: true 26 | arguments: 27 | - cowsay 28 | - Hello World 29 | 30 | upgrade: 31 | - docker: 32 | run: 33 | image: "ghcr.io/getporter/examples/images/whalesay:latest" 34 | rm: true 35 | arguments: 36 | - cowsay 37 | - World 2.0 38 | 39 | say: 40 | - docker: 41 | run: 42 | image: "ghcr.io/getporter/examples/images/whalesay:latest" 43 | rm: true 44 | arguments: 45 | - cowsay 46 | - "{{ bundle.parameters.msg }}" 47 | 48 | uninstall: 49 | - docker: 50 | run: 51 | image: "ghcr.io/getporter/examples/images/whalesay:latest" 52 | rm: true 53 | arguments: 54 | - cowsay 55 | - Goodbye World 56 | -------------------------------------------------------------------------------- /dockerapp/.gitignore: -------------------------------------------------------------------------------- 1 | .cnab -------------------------------------------------------------------------------- /dockerapp/README.md: -------------------------------------------------------------------------------- 1 | # Migrate from Docker App to Porter 2 | 3 | This bundle shows how to [Migrate from Docker App to Porter](https://porter.sh/blog/migrate-from-docker-app/). 4 | -------------------------------------------------------------------------------- /dockerapp/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.6" 2 | services: 3 | hello: 4 | image: hashicorp/http-echo 5 | command: ["-text", "${hello_text}"] 6 | ports: 7 | - ${hello_port}:5678 -------------------------------------------------------------------------------- /dockerapp/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/dockerapp 3 | version: 0.2.0 4 | description: "An example bundle that demonstrates how to move from Docker App to Porter" 5 | registry: ghcr.io/getporter 6 | 7 | required: 8 | - docker: 9 | privileged: false # Change to true if you need privileged containers 10 | 11 | parameters: 12 | - name: hello_text 13 | type: string 14 | env: hello_text 15 | default: hello from porter 16 | - name: hello_port 17 | type: integer 18 | env: hello_port 19 | default: 8080 20 | - name: context 21 | type: string 22 | default: default 23 | 24 | mixins: 25 | - docker-compose 26 | 27 | install: 28 | - docker-compose: 29 | arguments: 30 | - up 31 | - -d 32 | 33 | upgrade: 34 | - docker-compose: 35 | arguments: 36 | - up 37 | - -d 38 | 39 | uninstall: 40 | - docker-compose: 41 | arguments: 42 | - down 43 | -------------------------------------------------------------------------------- /exec-outputs/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl 5 | -------------------------------------------------------------------------------- /exec-outputs/.gitignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .cnab/ 3 | -------------------------------------------------------------------------------- /exec-outputs/Dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | FROM debian:stable-slim 2 | 3 | ARG BUNDLE_DIR 4 | 5 | # Install jq, we aren't using the mixin because it's an example bundle and jq 6 | # isn't a default mixin 7 | RUN apt-get update && apt-get install gnupg2 jq -y && rm -rf /var/lib/apt/lists/* 8 | 9 | # Use the BUNDLE_DIR build argument to copy files into the bundle 10 | COPY . ${BUNDLE_DIR} 11 | -------------------------------------------------------------------------------- /exec-outputs/README.md: -------------------------------------------------------------------------------- 1 | # Exec Outputs 2 | 3 | This bundle demonstrates how to use outputs with the exec mixin. Most mixins 4 | are based on the exec mixin, so you can use what you learn here with them. 5 | 6 | It also demonstrates how to use a [helper script with the exec mixin](https://porter.sh/best-practices/exec-mixin/#use-scripts). -------------------------------------------------------------------------------- /exec-outputs/cluster.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | create-cluster() { 6 | echo "Creating the cluster..." 7 | # This pretends to create a kubernetes cluster 8 | # by generating a dummy kubeconfig file 9 | mkdir -p /home/nonroot/.kube 10 | cat <> /home/nonroot/.kube/config 11 | apiVersion: v1 12 | clusters: 13 | - cluster: 14 | certificate-authority-data: abc123== 15 | server: https://127.0.0.1:8443 16 | name: minikube 17 | contexts: 18 | - context: 19 | cluster: minikube 20 | user: minikube 21 | name: minikube 22 | current-context: minikube 23 | kind: Config 24 | preferences: {} 25 | users: 26 | - name: minikube 27 | user: 28 | client-certificate-data: abc123= 29 | client-key-data: abc123== 30 | EOF 31 | } 32 | 33 | ensure-config() { 34 | if [ ! -f "/home/nonroot/.kube/config" ]; then 35 | echo "kubeconfig not found" 36 | exit 1 37 | fi 38 | } 39 | 40 | ensure-users() { 41 | if [ ! -f "/cnab/app/users.json" ]; then 42 | echo "users.json not found" 43 | exit 1 44 | fi 45 | } 46 | 47 | generate-users() { 48 | ensure-config 49 | echo '{"users": ["sally"]}' > users.json 50 | cat users.json 51 | } 52 | 53 | add-user() { 54 | ensure-config 55 | ensure-users 56 | # Do this in two steps because jq doesn't have overwrite functionality 57 | cat users.json | jq ".users += [\"$1\"]" > users.json.tmp 58 | # Using cp instead of mv because if the bundle is executed with Kubernetes 59 | # you can't move a file that was volume mounted in k8s. That only works with Docker. 60 | cp users.json.tmp users.json 61 | } 62 | 63 | dump-users() { 64 | ensure-config 65 | ensure-users 66 | cat users.json 67 | } 68 | 69 | uninstall() { 70 | ensure-config 71 | echo 'Uninstalling Cluster...' 72 | } 73 | 74 | # Call the requested function and pass the arguments as-is 75 | "$@" -------------------------------------------------------------------------------- /exec-outputs/config.json: -------------------------------------------------------------------------------- 1 | {"user": "sally"} 2 | -------------------------------------------------------------------------------- /exec-outputs/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/exec-outputs 3 | version: 0.2.0 4 | description: "An example Porter bundle demonstrating exec mixin outputs" 5 | registry: ghcr.io/getporter 6 | dockerfile: Dockerfile.tmpl 7 | 8 | mixins: 9 | - exec 10 | 11 | parameters: 12 | - name: kubeconfig 13 | type: file 14 | applyTo: 15 | - add-user 16 | - get-users 17 | - test 18 | - uninstall 19 | path: /home/nonroot/.kube/config 20 | source: 21 | output: kubeconfig 22 | - name: users.json 23 | type: file 24 | applyTo: 25 | - add-user 26 | - get-users 27 | path: /cnab/app/users.json 28 | source: 29 | output: users.json 30 | - name: username 31 | type: string 32 | default: wei 33 | applyTo: 34 | - add-user 35 | 36 | install: 37 | - exec: 38 | description: "Create the kubeconfig" 39 | command: ./cluster.sh 40 | arguments: 41 | - create-cluster 42 | - exec: 43 | description: "Create users file" 44 | command: ./cluster.sh 45 | arguments: 46 | - generate-users 47 | outputs: 48 | - name: admin 49 | jsonPath: "$.users[0]" 50 | - exec: 51 | description: "Use a step-level output" 52 | command: echo 53 | arguments: 54 | - "The admin user is: {{ bundle.outputs.admin }}" 55 | 56 | add-user: 57 | - exec: 58 | description: "Edit a bundle-level output passed in as a parameter" 59 | command: ./cluster.sh 60 | arguments: 61 | - add-user 62 | - "{{ bundle.parameters.username }}" 63 | 64 | test: 65 | - exec: 66 | description: "Scrape stdout with regex" 67 | command: ./run-tests.sh 68 | outputs: 69 | - name: failed-tests 70 | regex: '--- FAIL: (.*) \(.*\)' 71 | 72 | get-users: 73 | - exec: 74 | description: "Parse stdout with jsonPath" 75 | command: ./cluster.sh 76 | arguments: 77 | - dump-users 78 | outputs: 79 | - name: user-names 80 | jsonPath: '$.users' 81 | 82 | uninstall: 83 | - exec: 84 | description: "Uninstall bundle" 85 | command: ./cluster.sh 86 | arguments: 87 | - uninstall 88 | 89 | outputs: 90 | - name: users.json 91 | type: file 92 | path: /cnab/app/users.json 93 | applyTo: 94 | - install 95 | - add-user 96 | - name: failed-tests 97 | type: string 98 | applyTo: 99 | - test 100 | - name: user-names 101 | type: string 102 | applyTo: 103 | - get-users 104 | - name: kubeconfig 105 | type: file 106 | path: /home/nonroot/.kube/config 107 | applyTo: 108 | - install 109 | -------------------------------------------------------------------------------- /exec-outputs/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cat << EOF 4 | --- FAIL: TestInstall (0.10s) 5 | stuff 6 | things 7 | --- FAIL: TestUpgrade (0.01s) 8 | more 9 | logs 10 | EOF -------------------------------------------------------------------------------- /gke/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl 5 | -------------------------------------------------------------------------------- /gke/.gitignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .cnab/ 3 | -------------------------------------------------------------------------------- /gke/Dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | FROM debian:stable-slim 2 | 3 | ARG BUNDLE_DIR 4 | ENV GOOGLE_APPLICATION_CREDENTIALS=/home/nonroot/google-service-account.json 5 | 6 | COPY . ${BUNDLE_DIR} -------------------------------------------------------------------------------- /gke/README.md: -------------------------------------------------------------------------------- 1 | # GKE Kubernetes + Exec Mixin Example 2 | 3 | This is a sample Porter bundle that makes use of both the Kubernetes and Exec 4 | mixins to apply manifests to a GKE Kubernetes cluster. 5 | The Kubernetes mixin is used to apply Kubernetes manifests to an 6 | existing Kubernetes cluster, creating an NGINX deployment and a service. The 7 | Kubernetes mixin is also used to produce an output with the value of the 8 | service's ClusterIP. After the `kubernetes` mixin finishes, the `exec` mixin is 9 | ued to echo the cluster IP of the service that was created. 10 | 11 | To use this bundle, you will need an existing Kubernetes cluster and a 12 | kubeconfig file for use as a credential. 13 | 14 | ``` 15 | porter build 16 | porter credentials generate 17 | porter install -c gke-example 18 | ``` 19 | -------------------------------------------------------------------------------- /gke/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | dump-ip() { 5 | echo You will find the service at: $1 6 | } 7 | 8 | # Call the requested function and pass the arguments as-is 9 | "$@" 10 | -------------------------------------------------------------------------------- /gke/manifests/nginx/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: nginx-deployment 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: nginx 9 | replicas: 2 # tells deployment to run 2 pods matching the template 10 | template: 11 | metadata: 12 | labels: 13 | app: nginx 14 | spec: 15 | containers: 16 | - name: nginx 17 | image: nginx:1.7.9 18 | ports: 19 | - containerPort: 80 20 | ports: 21 | - containerPort: 80 22 | -------------------------------------------------------------------------------- /gke/manifests/nginx/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: nginx-deployment 5 | namespace: default 6 | spec: 7 | ports: 8 | - port: 80 9 | protocol: TCP 10 | targetPort: 80 11 | selector: 12 | app: nginx 13 | sessionAffinity: None 14 | type: ClusterIP 15 | -------------------------------------------------------------------------------- /gke/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/gke 3 | version: 0.2.0 4 | description: "An example Porter bundle with Kubernetes" 5 | registry: ghcr.io/getporter 6 | dockerfile: Dockerfile.tmpl 7 | 8 | credentials: 9 | - name: kubeconfig 10 | path: /home/nonroot/.kube/config 11 | - name: google-service-account 12 | path: /home/nonroot/google-service-account.json 13 | 14 | mixins: 15 | - exec 16 | - kubernetes 17 | 18 | install: 19 | - kubernetes: 20 | description: "Create NGINX Deployment" 21 | manifests: 22 | - manifests/nginx 23 | wait: true 24 | outputs: 25 | - name: IP_ADDRESS 26 | resourceType: service 27 | resourceName: nginx-deployment 28 | jsonPath: "{.spec.clusterIP}" 29 | - exec: 30 | description: "Echo the IP Address" 31 | command: ./helpers.sh 32 | arguments: 33 | - "dump-ip {{bundle.outputs.IP_ADDRESS}}" 34 | 35 | uninstall: 36 | - kubernetes: 37 | description: "Uninstall Hello World" 38 | manifests: 39 | - manifests/nginx 40 | wait: true 41 | 42 | outputs: 43 | - name: IP_ADDRESS 44 | type: string 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module get.porter.sh/example-bundles 2 | 3 | go 1.21 4 | 5 | toolchain go1.21.3 6 | 7 | require ( 8 | get.porter.sh/magefiles v0.6.8 9 | github.com/carolynvs/magex v0.9.0 10 | github.com/hashicorp/go-multierror v1.1.1 11 | github.com/magefile/mage v1.15.0 12 | github.com/stretchr/testify v1.8.0 13 | golang.org/x/sync v0.0.0-20220907140024-f12130a52804 14 | gopkg.in/yaml.v3 v3.0.1 15 | ) 16 | 17 | require ( 18 | github.com/Masterminds/semver/v3 v3.2.1 // indirect 19 | github.com/andybalholm/brotli v1.1.0 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect 22 | github.com/golang/snappy v0.0.4 // indirect 23 | github.com/hashicorp/errwrap v1.1.0 // indirect 24 | github.com/klauspost/compress v1.17.7 // indirect 25 | github.com/klauspost/pgzip v1.2.6 // indirect 26 | github.com/kr/pretty v0.3.0 // indirect 27 | github.com/mholt/archiver/v3 v3.5.1 // indirect 28 | github.com/nwaples/rardecode v1.1.3 // indirect 29 | github.com/pierrec/lz4/v4 v4.1.21 // indirect 30 | github.com/pmezard/go-difflib v1.0.0 // indirect 31 | github.com/rogpeppe/go-internal v1.8.1 // indirect 32 | github.com/ulikunitz/xz v0.5.11 // indirect 33 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | get.porter.sh/magefiles v0.3.2 h1:RDJini5LBQ8pYnc0r8M6cX2Pk+CPsCtyFwO3fy8Jb5g= 2 | get.porter.sh/magefiles v0.3.2/go.mod h1:w8ikniAFHO7AVvWayyWF7ViHoh4tNKwTxtKpO1FyGkU= 3 | get.porter.sh/magefiles v0.6.8 h1:1q0CmKgOtlP8IBXtRNLv2+r9tLiC96tpIPhTHX+HlUw= 4 | get.porter.sh/magefiles v0.6.8/go.mod h1:w37oTKICvvaEKR5KVB9UfN2EX30uYO9Qk0oRoz80DOU= 5 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= 6 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 7 | github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= 8 | github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= 9 | github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= 10 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 11 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 12 | github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= 13 | github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= 14 | github.com/carolynvs/magex v0.9.0 h1:fWe7oshGv6zuei5Z6EI95RSlOKjIifBZ26myB9G+m/I= 15 | github.com/carolynvs/magex v0.9.0/go.mod h1:H1LW6RYJ/sNbisMmPe9E73aJZa8geKLKK9mBWLWz3ek= 16 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= 21 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= 22 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= 23 | github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 24 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 25 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 26 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 27 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 28 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 29 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 30 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 31 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 32 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 33 | github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 34 | github.com/klauspost/compress v1.15.10 h1:Ai8UzuomSCDw90e1qNMtb15msBXsNpH6gzkkENQNcJo= 35 | github.com/klauspost/compress v1.15.10/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= 36 | github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= 37 | github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 38 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 39 | github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= 40 | github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 41 | github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= 42 | github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 43 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 44 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 45 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 46 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 47 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 48 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 49 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 50 | github.com/magefile/mage v1.13.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= 51 | github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= 52 | github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= 53 | github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= 54 | github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= 55 | github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= 56 | github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= 57 | github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 58 | github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= 59 | github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 60 | github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 61 | github.com/pierrec/lz4/v4 v4.1.16 h1:kQPfno+wyx6C5572ABwV+Uo3pDFzQ7yhyGchSyRda0c= 62 | github.com/pierrec/lz4/v4 v4.1.16/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 63 | github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= 64 | github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 65 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 66 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 67 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 68 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 69 | github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= 70 | github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= 71 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 72 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 73 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 74 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 75 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 76 | github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 77 | github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 78 | github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= 79 | github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 80 | github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= 81 | github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 82 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= 83 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= 84 | golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A= 85 | golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 86 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 87 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 88 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 89 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 90 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 91 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 92 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 93 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 94 | -------------------------------------------------------------------------------- /hello/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl 5 | -------------------------------------------------------------------------------- /hello/.gitignore: -------------------------------------------------------------------------------- 1 | .cnab/ 2 | -------------------------------------------------------------------------------- /hello/README.md: -------------------------------------------------------------------------------- 1 | # Example: Hello World 2 | 3 | Try out this example at https://porter.sh/examples/hello/. 4 | -------------------------------------------------------------------------------- /hello/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | name=$(cat /cnab/app/foo/name.txt) 5 | 6 | install() { 7 | echo "Hello, $name" 8 | } 9 | 10 | upgrade() { 11 | echo "Hello, $name" 12 | } 13 | 14 | uninstall() { 15 | echo "Goodbye, $name" 16 | } 17 | 18 | # Call the requested function and pass the arguments as-is 19 | "$@" 20 | -------------------------------------------------------------------------------- /hello/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/porter-hello 3 | version: 0.2.0 4 | description: "An example Porter configuration" 5 | registry: ghcr.io/getporter 6 | 7 | parameters: 8 | - name: name 9 | type: string 10 | default: porter 11 | path: /cnab/app/foo/name.txt 12 | source: 13 | output: name 14 | 15 | outputs: 16 | - name: name 17 | path: /cnab/app/foo/name.txt 18 | 19 | mixins: 20 | - exec 21 | 22 | install: 23 | - exec: 24 | description: "Install Hello World" 25 | command: ./helpers.sh 26 | arguments: 27 | - install 28 | 29 | upgrade: 30 | - exec: 31 | description: "World 2.0" 32 | command: ./helpers.sh 33 | arguments: 34 | - upgrade 35 | 36 | uninstall: 37 | - exec: 38 | description: "Uninstall Hello World" 39 | command: ./helpers.sh 40 | arguments: 41 | - uninstall 42 | 43 | -------------------------------------------------------------------------------- /helm-multiarch/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | template.Dockerfile 5 | -------------------------------------------------------------------------------- /helm-multiarch/.gitignore: -------------------------------------------------------------------------------- 1 | .cnab/ 2 | -------------------------------------------------------------------------------- /helm-multiarch/README.md: -------------------------------------------------------------------------------- 1 | # Deploy a multi-arch image with Helm 2 | 3 | While Porter bundles can only run on amd64 hosts, you can deploy mult-arch images to other platforms, such as an ARM64 environment. 4 | This example demonstrates how to deploy a helm chart that uses a multi-arch image to an ARM64 kubernetes cluster. 5 | 6 | 1. Create a file named "mycluster.yaml" with the following contents. Edit the path to the kubeconfig file to the location where your kubeconfig file. 7 | 8 | ``` 9 | schemaType: CredentialSet 10 | schemaVersion: 1.0.1 11 | name: mycluster 12 | credentials: 13 | - name: kubeconfig 14 | source: 15 | path: $HOME/.kind/config # TODO: Edit this path 16 | ``` 17 | 2. Apply the credential set 18 | ``` 19 | porter credentials apply mycluster.yaml 20 | ``` 21 | 3. Install the bundle 22 | ``` 23 | porter install -r ghcr.io/getporter/examples/helm-multiarch:v0.1.0 -c mycluster 24 | ``` 25 | 26 | Now that the bundle is installed successfully, let's verify that the deployed image was ARM64. 27 | 28 | 1. Ensure that your KUBECONFIG environment variable is pointing to the cluster to which you just deployed. 29 | 2. Get the image that was deployed and save that value to an environment variable 30 | ``` 31 | IMG=`k get pods -o yaml -l app.kubernetes.io/instance=porter-helm-nginx -o=jsonpath='{.items[0].status.containerStatuses[0].imageID}'` 32 | ``` 33 | 3. Pull the deployed image so that you can inspect it 34 | ``` 35 | docker pull $IMG 36 | ``` 37 | 4. Inspect it to see the architecture of the image 38 | ``` 39 | $ docker image inspect $IMG -f "{{.Architecture}}" 40 | arm64 41 | ``` 42 | 43 | As you can see, while Porter bundles can't run directly on a ARM64 host, they can deploy to ARM64 environments using multi-arch images! 🎉 44 | -------------------------------------------------------------------------------- /helm-multiarch/charts/nginx/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /helm-multiarch/charts/nginx/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: nginx 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | -------------------------------------------------------------------------------- /helm-multiarch/charts/nginx/README.md: -------------------------------------------------------------------------------- 1 | This is the default chart generated by `helm create nginx`. The deployment template has been modified to deploy using the image digest instead of tag. 2 | -------------------------------------------------------------------------------- /helm-multiarch/charts/nginx/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "nginx.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "nginx.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "nginx.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "nginx.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /helm-multiarch/charts/nginx/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "nginx.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "nginx.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "nginx.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "nginx.labels" -}} 37 | helm.sh/chart: {{ include "nginx.chart" . }} 38 | {{ include "nginx.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "nginx.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "nginx.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "nginx.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "nginx.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /helm-multiarch/charts/nginx/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "nginx.fullname" . }} 5 | labels: 6 | {{- include "nginx.labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "nginx.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "nginx.selectorLabels" . | nindent 8 }} 22 | spec: 23 | {{- with .Values.imagePullSecrets }} 24 | imagePullSecrets: 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | serviceAccountName: {{ include "nginx.serviceAccountName" . }} 28 | securityContext: 29 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 30 | containers: 31 | - name: {{ .Chart.Name }} 32 | securityContext: 33 | {{- toYaml .Values.securityContext | nindent 12 }} 34 | image: "{{ .Values.image.repository }}@{{ .Values.image.digest }}" 35 | imagePullPolicy: {{ .Values.image.pullPolicy }} 36 | ports: 37 | - name: http 38 | containerPort: 80 39 | protocol: TCP 40 | livenessProbe: 41 | httpGet: 42 | path: / 43 | port: http 44 | readinessProbe: 45 | httpGet: 46 | path: / 47 | port: http 48 | resources: 49 | {{- toYaml .Values.resources | nindent 12 }} 50 | {{- with .Values.nodeSelector }} 51 | nodeSelector: 52 | {{- toYaml . | nindent 8 }} 53 | {{- end }} 54 | {{- with .Values.affinity }} 55 | affinity: 56 | {{- toYaml . | nindent 8 }} 57 | {{- end }} 58 | {{- with .Values.tolerations }} 59 | tolerations: 60 | {{- toYaml . | nindent 8 }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /helm-multiarch/charts/nginx/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "nginx.fullname" . }} 6 | labels: 7 | {{- include "nginx.labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "nginx.fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /helm-multiarch/charts/nginx/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "nginx.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "nginx.labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - host: {{ .host | quote }} 42 | http: 43 | paths: 44 | {{- range .paths }} 45 | - path: {{ .path }} 46 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 47 | pathType: {{ .pathType }} 48 | {{- end }} 49 | backend: 50 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 51 | service: 52 | name: {{ $fullName }} 53 | port: 54 | number: {{ $svcPort }} 55 | {{- else }} 56 | serviceName: {{ $fullName }} 57 | servicePort: {{ $svcPort }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /helm-multiarch/charts/nginx/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "nginx.fullname" . }} 5 | labels: 6 | {{- include "nginx.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "nginx.selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /helm-multiarch/charts/nginx/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "nginx.serviceAccountName" . }} 6 | labels: 7 | {{- include "nginx.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /helm-multiarch/charts/nginx/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "nginx.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "nginx.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "nginx.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /helm-multiarch/charts/nginx/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for nginx. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: nginx 9 | pullPolicy: IfNotPresent 10 | digest: "" 11 | 12 | imagePullSecrets: [] 13 | nameOverride: "" 14 | fullnameOverride: "" 15 | 16 | serviceAccount: 17 | # Specifies whether a service account should be created 18 | create: true 19 | # Annotations to add to the service account 20 | annotations: {} 21 | # The name of the service account to use. 22 | # If not set and create is true, a name is generated using the fullname template 23 | name: "" 24 | 25 | podAnnotations: {} 26 | 27 | podSecurityContext: {} 28 | # fsGroup: 2000 29 | 30 | securityContext: {} 31 | # capabilities: 32 | # drop: 33 | # - ALL 34 | # readOnlyRootFilesystem: true 35 | # runAsNonRoot: true 36 | # runAsUser: 1000 37 | 38 | service: 39 | type: ClusterIP 40 | port: 80 41 | 42 | ingress: 43 | enabled: false 44 | className: "" 45 | annotations: {} 46 | # kubernetes.io/ingress.class: nginx 47 | # kubernetes.io/tls-acme: "true" 48 | hosts: 49 | - host: chart-example.local 50 | paths: 51 | - path: / 52 | pathType: ImplementationSpecific 53 | tls: [] 54 | # - secretName: chart-example-tls 55 | # hosts: 56 | # - chart-example.local 57 | 58 | resources: {} 59 | # We usually recommend not to specify default resources and to leave this as a conscious 60 | # choice for the user. This also increases chances charts run on environments with little 61 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 62 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 63 | # limits: 64 | # cpu: 100m 65 | # memory: 128Mi 66 | # requests: 67 | # cpu: 100m 68 | # memory: 128Mi 69 | 70 | autoscaling: 71 | enabled: false 72 | minReplicas: 1 73 | maxReplicas: 100 74 | targetCPUUtilizationPercentage: 80 75 | # targetMemoryUtilizationPercentage: 80 76 | 77 | nodeSelector: {} 78 | 79 | tolerations: [] 80 | 81 | affinity: {} 82 | -------------------------------------------------------------------------------- /helm-multiarch/porter.yaml: -------------------------------------------------------------------------------- 1 | # Version of the porter.yaml schema used by this file 2 | # Requires porter v1.0.0+ 3 | schemaVersion: 1.0.0 4 | 5 | # Name of the bundle 6 | name: examples/helm-multiarch 7 | 8 | # Version of the bundle. Change this each time you modify a published bundle. 9 | version: 0.1.0 10 | 11 | # Description of the bundle and what it does. 12 | description: "Deploy nginx (multi-arch) to a cluster with Porter" 13 | 14 | # Registry where the bundle is published to by default 15 | registry: "ghcr.io/getporter" 16 | 17 | # Declare and optionally configure the mixins used by the bundle 18 | mixins: 19 | - exec 20 | - helm3: 21 | clientVersion: v3.10.1 22 | 23 | credentials: 24 | - name: kubeconfig 25 | path: /home/nonroot/.kube/config 26 | 27 | images: 28 | nginx: 29 | repository: nginx 30 | imageType: docker 31 | # This tag points to a manifest for a multi-arch image 32 | # All arch specific images referenced in the manifest are relocated with the bundle 33 | # So publishing will be slower than you'd expect (it's copying 8 nginx images, not just 1) 34 | tag: latest 35 | 36 | # Define the steps that should execute when the bundle is installed 37 | install: 38 | - helm3: 39 | description: Install nginx 40 | chart: ./charts/nginx 41 | namespace: default 42 | name: porter-helm-nginx 43 | set: 44 | # Instruct helm to use the relocated multi-arch image 45 | # Kubernetes will handle selecting the appropriate arch specific digest associated with the image 46 | # as long as we pass it the multi-arch image reference and not a reference to a specific architecture. 47 | image.repository: ${ bundle.images.nginx.repository } 48 | image.digest: ${ bundle.images.nginx.digest } 49 | 50 | # Define the steps that should execute when the bundle is upgraded 51 | upgrade: 52 | - helm3: 53 | description: Upgrade nginx 54 | chart: ./charts/nginx 55 | namespace: default 56 | name: porter-helm-nginx 57 | set: 58 | image.repository: ${ bundle.images.nginx.repository } 59 | image.digest: ${ bundle.images.nginx.digest } 60 | 61 | # Define the steps that should execute when the bundle is uninstalled 62 | uninstall: 63 | - helm3: 64 | description: Uninstall nginx 65 | namespace: default 66 | releases: 67 | - porter-helm-nginx 68 | -------------------------------------------------------------------------------- /images/README.md: -------------------------------------------------------------------------------- 1 | # Porter Example Images 2 | 3 | A collection of images used within the porter examples, and published to the getporter/examples/images container registry 4 | -------------------------------------------------------------------------------- /images/whalesay/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | RUN apk add --no-cache perl 3 | COPY cowsay /usr/local/bin/cowsay 4 | COPY docker.cow /usr/local/share/cows/default.cow 5 | ENTRYPOINT ["/usr/local/bin/cowsay"] 6 | -------------------------------------------------------------------------------- /images/whalesay/LICENSE: -------------------------------------------------------------------------------- 1 | ============== 2 | cowsay License 3 | ============== 4 | 5 | cowsay is distributed under the same licensing terms as Perl: the 6 | Artistic License or the GNU General Public License. If you don't 7 | want to track down these licenses and read them for yourself, use 8 | the parts that I'd prefer: 9 | 10 | (0) I wrote it and you didn't. 11 | 12 | (1) Give credit where credit is due if you borrow the code for some 13 | other purpose. 14 | 15 | (2) If you have any bugfixes or suggestions, please notify me so 16 | that I may incorporate them. 17 | 18 | (3) If you try to make money off of cowsay, you suck. 19 | 20 | =============== 21 | cowsay Legalese 22 | =============== 23 | 24 | (0) Copyright (c) 1999 Tony Monroe. All rights reserved. All 25 | lefts may or may not be reversed at my discretion. 26 | 27 | (1) This software package can be freely redistributed or modified 28 | under the terms described above in the "cowsay License" section 29 | of this file. 30 | 31 | (2) cowsay is provided "as is," with no warranties whatsoever, 32 | expressed or implied. If you want some implied warranty about 33 | merchantability and/or fitness for a particular purpose, you will 34 | not find it here, because there is no such thing here. 35 | 36 | (3) I hate legalese. 37 | -------------------------------------------------------------------------------- /images/whalesay/Makefile: -------------------------------------------------------------------------------- 1 | REGISTRY?=ghcr.io/getporter/examples/images/whalesay 2 | LATEST=$(REGISTRY):latest 3 | 4 | build: 5 | docker build -t $(LATEST) . 6 | 7 | publish: build 8 | docker push $(LATEST) 9 | -------------------------------------------------------------------------------- /images/whalesay/cowsay: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | ## 4 | ## Cowsay 3.03 5 | ## 6 | ## This file is part of cowsay. (c) 1999-2000 Tony Monroe. 7 | ## 8 | 9 | use Text::Tabs qw(expand); 10 | use Text::Wrap qw(wrap fill $columns); 11 | use File::Basename; 12 | use Getopt::Std; 13 | use Cwd; 14 | 15 | $version = "3.03"; 16 | $progname = basename($0); 17 | $eyes = "oo"; 18 | $tongue = " "; 19 | $cowpath = $ENV{'COWPATH'} || '/usr/local/share/cows'; 20 | @message = (); 21 | $thoughts = ""; 22 | 23 | ## Yeah, this is rude, I know. But hopefully it gets around a nasty 24 | ## little version dependency. 25 | 26 | $Text::Wrap::initial_tab = 8; 27 | $Text::Wrap::subsequent_tab = 8; 28 | $Text::Wrap::tabstop = 8; 29 | 30 | ## One of these days, we'll get it ported to Windows. Yeah, right. 31 | 32 | if (($^O eq "MSWin32") or ($^O eq "Windows_NT")) { ## Many perls, eek! 33 | $pathsep = ';'; 34 | } else { 35 | $pathsep = ':'; 36 | } 37 | 38 | %opts = ( 39 | 'e' => 'oo', 40 | 'f' => 'default.cow', 41 | 'n' => 0, 42 | 'T' => ' ', 43 | 'W' => 40, 44 | ); 45 | 46 | getopts('bde:f:ghlLnNpstT:wW:y', \%opts); 47 | 48 | &display_usage if $opts{'h'}; 49 | &list_cowfiles if $opts{'l'}; 50 | 51 | $borg = $opts{'b'}; 52 | $dead = $opts{'d'}; 53 | $greedy = $opts{'g'}; 54 | $paranoid = $opts{'p'}; 55 | $stoned = $opts{'s'}; 56 | $tired = $opts{'t'}; 57 | $wired = $opts{'w'}; 58 | $young = $opts{'y'}; 59 | $eyes = substr($opts{'e'}, 0, 2); 60 | $tongue = substr($opts{'T'}, 0, 2); 61 | $the_cow = ""; 62 | 63 | &slurp_input; 64 | $Text::Wrap::columns = $opts{'W'}; 65 | @message = ($opts{'n'} ? expand(@message) : 66 | split("\n", fill("", "", @message))); 67 | &construct_balloon; 68 | &construct_face; 69 | &get_cow; 70 | print @balloon_lines; 71 | print $the_cow; 72 | 73 | sub list_cowfiles { 74 | my $basedir; 75 | my @dirfiles; 76 | chop($basedir = cwd); 77 | for my $d (split(/$pathsep/, $cowpath)) { 78 | print "Cow files in $d:\n"; 79 | opendir(COWDIR, $d) || die "$0: Cannot open $d\n"; 80 | for my $file (readdir COWDIR) { 81 | if ($file =~ s/\.cow$//) { 82 | push(@dirfiles, $file); 83 | } 84 | } 85 | closedir(COWDIR); 86 | print wrap("", "", sort @dirfiles), "\n"; 87 | @dirfiles = (); 88 | chdir($basedir); 89 | } 90 | exit(0); 91 | } 92 | 93 | sub slurp_input { 94 | unless ($ARGV[0]) { 95 | chomp(@message = ); 96 | } else { 97 | &display_usage if $opts{'n'}; 98 | @message = join(' ', @ARGV); 99 | } 100 | } 101 | 102 | sub maxlength { 103 | my ($l, $m); 104 | $m = -1; 105 | for my $i (@_) { 106 | $l = length $i; 107 | $m = $l if ($l > $m); 108 | } 109 | return $m; 110 | } 111 | 112 | sub construct_balloon { 113 | my $max = &maxlength(@message); 114 | my $max2 = $max + 2; ## border space fudge. 115 | my $format = "%s %-${max}s %s\n"; 116 | my @border; ## up-left, up-right, down-left, down-right, left, right 117 | if ($0 =~ /think/i) { 118 | $thoughts = 'o'; 119 | @border = qw[ ( ) ( ) ( ) ]; 120 | } elsif (@message < 2) { 121 | $thoughts = '\\'; 122 | @border = qw[ < > ]; 123 | } else { 124 | $thoughts = '\\'; 125 | if ($V and $V gt v5.6.0) { # Thanks, perldelta. 126 | @border = qw[ / \\ \\ / | | ]; 127 | } else { 128 | @border = qw[ / \ \ / | | ]; 129 | } 130 | } 131 | push(@balloon_lines, 132 | " " . ("_" x $max2) . " \n" , 133 | sprintf($format, $border[0], $message[0], $border[1]), 134 | (@message < 2 ? "" : 135 | map { sprintf($format, $border[4], $_, $border[5]) } 136 | @message[1 .. $#message - 1]), 137 | (@message < 2 ? "" : 138 | sprintf($format, $border[2], $message[$#message], $border[3])), 139 | " " . ("-" x $max2) . " \n" 140 | ); 141 | } 142 | 143 | sub construct_face { 144 | if ($borg) { $eyes = "=="; } 145 | if ($dead) { $eyes = "xx"; $tongue = "U "; } 146 | if ($greedy) { $eyes = "\$\$"; } 147 | if ($paranoid) { $eyes = "@@"; } 148 | if ($stoned) { $eyes = "**"; $tongue = "U "; } 149 | if ($tired) { $eyes = "--"; } 150 | if ($wired) { $eyes = "OO"; } 151 | if ($young) { $eyes = ".."; } 152 | } 153 | 154 | sub get_cow { 155 | ## 156 | ## Get a cow from the specified cowfile; otherwise use the default cow 157 | ## which was defined above in $the_cow. 158 | ## 159 | my $f = $opts{'f'}; 160 | my $full = ""; 161 | if ($opts{'f'} =~ m,/,) { 162 | $full = $opts{'f'}; 163 | } else { 164 | for my $d (split(/:/, $cowpath)) { 165 | if (-f "$d/$f") { 166 | $full = "$d/$f"; 167 | last; 168 | } elsif (-f "$d/$f.cow") { 169 | $full = "$d/$f.cow"; 170 | last; 171 | } 172 | } 173 | if ($full eq "") { 174 | die "$progname: Could not find $f cowfile!\n"; 175 | } 176 | } 177 | do $full; 178 | die "$progname: $@\n" if $@; 179 | } 180 | 181 | sub display_usage { 182 | die < 11 | 12 | ## Try it out 13 | 14 | ```bash 15 | docker run -d -e DEFAULT_MESSAGE="whale aren't you just precious?" -p 8080:8080 ghcr.io/getporter/examples/images/whalesayd 16 | ``` 17 | 18 | ```bash 19 | $ curl http://localhost:8080?msg=time+for+a+nap! 20 | _________________ 21 | < time for a nap! > 22 | ----------------- 23 | \ 24 | \ 25 | \ 26 | ## . 27 | ## ## ## == 28 | ## ## ## ## === 29 | /""""""""""""""""___/ === 30 | ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~ 31 | \______ o __/ 32 | \ \ __/ 33 | \____\______/ 34 | ``` 35 | 36 | [whalesay]: https://github.com/orgs/getporter/packages/container/package/examples%2Fimages%2Fwhalesay 37 | -------------------------------------------------------------------------------- /images/whalesayd/go.mod: -------------------------------------------------------------------------------- 1 | module whalesayd 2 | 3 | go 1.22.2 4 | -------------------------------------------------------------------------------- /images/whalesayd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "net/http" 7 | "os" 8 | "os/exec" 9 | ) 10 | 11 | func main() { 12 | http.HandleFunc("/", Say) 13 | log.Fatal(http.ListenAndServe(":8080", nil)) 14 | } 15 | 16 | func Say(w http.ResponseWriter, r *http.Request) { 17 | var msg = "Whale Hello There!" 18 | if defaultMsg, ok := os.LookupEnv("DEFAULT_MESSAGE"); ok { 19 | msg = defaultMsg 20 | } 21 | userMsg := r.FormValue("msg") 22 | if userMsg != "" { 23 | msg = userMsg 24 | } 25 | cowsay := exec.Command("cowsay", msg) 26 | 27 | buf := bytes.Buffer{} 28 | cowsay.Stdout = &buf 29 | err := cowsay.Start() 30 | if err != nil { 31 | w.WriteHeader(500) 32 | w.Write([]byte("Oops, we couldn't get the whale started, sorry!")) 33 | return 34 | } 35 | 36 | err = cowsay.Wait() 37 | if err != nil { 38 | w.WriteHeader(500) 39 | w.Write([]byte("Oops, the whale ran outta steam, sorry!")) 40 | return 41 | } 42 | 43 | _, err = w.Write(buf.Bytes()) 44 | if err != nil { 45 | w.WriteHeader(500) 46 | w.Write([]byte("Oops, the whale is a bit tongue tied, sorry!")) 47 | return 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /kubernetes/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl 5 | -------------------------------------------------------------------------------- /kubernetes/.gitignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .cnab/ 3 | -------------------------------------------------------------------------------- /kubernetes/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes + Exec Mixin Example 2 | 3 | This is a sample Porter bundle that makes use of both the Kubernetes and Exec mixins. The Kubernetes mixin is used to apply Kubernetes manifests to an existing Kubernetes cluster, creating an NGINX deployment and a service. The Kubernetes mixin is also used to produce an output with the value of the service's ClusterIP. After the kubernetes mixin finishes, the `exec` mixin is ued to echo the cluster IP of the service that was created. 4 | 5 | To use this bundle, you will need an existing Kubernetes cluster and a kubeconfig file for use as a credential. 6 | 7 | ``` 8 | porter build 9 | porter credentials generate 10 | porter install -c kubernetes 11 | ``` 12 | 13 | 🚨 GKE requires additional credentials and parameters in order to [connect to a cluster][gke-connect], so use the [GKE Example](/examples/gke-example) instead. 14 | 15 | [gke-connect]: https://porter.sh/docs/integrations/gke/ 16 | -------------------------------------------------------------------------------- /kubernetes/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | dump-ip() { 5 | echo You will find the service at: $1 6 | } 7 | 8 | # Call the requested function and pass the arguments as-is 9 | "$@" 10 | -------------------------------------------------------------------------------- /kubernetes/manifests/nginx/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: nginx-deployment 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: nginx 9 | replicas: 2 # tells deployment to run 2 pods matching the template 10 | template: 11 | metadata: 12 | labels: 13 | app: nginx 14 | spec: 15 | containers: 16 | - name: nginx 17 | image: nginx:1.7.9 18 | ports: 19 | - containerPort: 80 20 | ports: 21 | - containerPort: 80 22 | -------------------------------------------------------------------------------- /kubernetes/manifests/nginx/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: nginx-deployment 5 | namespace: default 6 | spec: 7 | ports: 8 | - port: 80 9 | protocol: TCP 10 | targetPort: 80 11 | selector: 12 | app: nginx 13 | sessionAffinity: None 14 | type: ClusterIP 15 | -------------------------------------------------------------------------------- /kubernetes/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0 2 | name: examples/kubernetes 3 | version: 0.2.0 4 | description: "An example Porter bundle with Kubernetes" 5 | registry: ghcr.io/getporter 6 | 7 | mixins: 8 | - exec 9 | - kubernetes 10 | 11 | credentials: 12 | - name: kubeconfig 13 | path: /home/nonroot/.kube/config 14 | 15 | install: 16 | - kubernetes: 17 | description: "Create NGINX Deployment" 18 | manifests: 19 | - manifests/nginx 20 | wait: true 21 | outputs: 22 | - name: IP_ADDRESS 23 | resourceType: service 24 | resourceName: nginx-deployment 25 | jsonPath: "{.spec.clusterIP}" 26 | - exec: 27 | description: "Echo the IP Address" 28 | command: ./helpers.sh 29 | arguments: 30 | - dump-ip 31 | - ${bundle.outputs.IP_ADDRESS} 32 | 33 | uninstall: 34 | - kubernetes: 35 | description: "Uninstall Hello World" 36 | manifests: 37 | - manifests/nginx 38 | wait: true 39 | 40 | outputs: 41 | - name: IP_ADDRESS 42 | type: string 43 | applyTo: 44 | - install 45 | -------------------------------------------------------------------------------- /mage.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/magefile/mage/mage" 10 | ) 11 | 12 | // This file allows someone to run mage commands without mage installed 13 | // by running `go run mage.go TARGET`. 14 | // See https://magefile.org/zeroinstall/ 15 | func main() { os.Exit(mage.Main()) } 16 | -------------------------------------------------------------------------------- /mage/examples/examples.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "gopkg.in/yaml.v3" 9 | ) 10 | 11 | // GetBundleRef builds the reference to the specified example bundle, given a registry override. 12 | func GetBundleRef(bundleDir string, registryOverride string) (string, error) { 13 | manifestPath := filepath.Join(bundleDir, "porter.yaml") 14 | contents, err := os.ReadFile(manifestPath) 15 | if err != nil { 16 | return "", fmt.Errorf("error reading porter manifest at %s: %w", manifestPath, err) 17 | } 18 | 19 | manifest := map[string]interface{}{} 20 | if err = yaml.Unmarshal(contents, &manifest); err != nil { 21 | return "", fmt.Errorf("error parsing porter manifest at %s: %w", manifestPath, err) 22 | } 23 | 24 | name, ok := manifest["name"].(string) 25 | if !ok { 26 | return "", fmt.Errorf("name was not defined in %s", manifestPath) 27 | } 28 | 29 | version, ok := manifest["version"].(string) 30 | if !ok { 31 | return "", fmt.Errorf("version was not defined in %s", manifestPath) 32 | } 33 | 34 | registry, ok := manifest["registry"].(string) 35 | if !ok { 36 | return "", fmt.Errorf("registry was not defined in %s", manifestPath) 37 | } 38 | 39 | if registryOverride != "" { 40 | registry = registryOverride 41 | } 42 | 43 | return fmt.Sprintf("%s/%s:v%s", registry, name, version), nil 44 | } 45 | 46 | // List returns the names of all example bundles in the specified directory. 47 | func List(dir string) ([]string, error) { 48 | results, err := os.ReadDir(dir) 49 | if err != nil { 50 | return nil, fmt.Errorf("error listing example bundles in current directory: %w", err) 51 | } 52 | 53 | exampleNames := make([]string, 0, len(results)) 54 | for _, result := range results { 55 | if result.IsDir() { 56 | bundleName := result.Name() 57 | if _, err := os.Stat(filepath.Join(dir, bundleName, "porter.yaml")); err == nil { 58 | exampleNames = append(exampleNames, bundleName) 59 | } 60 | } 61 | } 62 | 63 | return exampleNames, nil 64 | } 65 | -------------------------------------------------------------------------------- /mage/examples/examples_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/carolynvs/magex/mgx" 8 | "github.com/carolynvs/magex/shx" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestListExampleBundles(t *testing.T) { 14 | tmp, err := os.MkdirTemp("", "example-bundles") 15 | require.NoError(t, err) 16 | defer os.RemoveAll(tmp) 17 | 18 | mgx.Must(shx.Copy("../../hello", tmp, shx.CopyRecursive)) 19 | mgx.Must(shx.Copy("../../mage", tmp, shx.CopyRecursive)) 20 | 21 | names, err := List(tmp) 22 | require.NoError(t, err) 23 | 24 | assert.Equal(t, []string{"hello"}, names) 25 | } 26 | 27 | func TestGetBundleRef(t *testing.T) { 28 | t.Run("valid manifest", func(t *testing.T) { 29 | ref, err := GetBundleRef("../../hello", "localhost:5000") 30 | require.NoError(t, err) 31 | assert.Equal(t, "localhost:5000/examples/porter-hello:v0.2.0", ref) 32 | }) 33 | 34 | t.Run("missing registry", func(t *testing.T) { 35 | _, err := GetBundleRef("testdata/missing-registry", "localhost:5000") 36 | assert.EqualError(t, err, "registry was not defined in testdata/missing-registry/porter.yaml") 37 | }) 38 | 39 | t.Run("missing name", func(t *testing.T) { 40 | _, err := GetBundleRef("testdata/missing-name", "localhost:5000") 41 | assert.EqualError(t, err, "name was not defined in testdata/missing-name/porter.yaml") 42 | }) 43 | 44 | t.Run("missing version", func(t *testing.T) { 45 | _, err := GetBundleRef("testdata/missing-version", "localhost:5000") 46 | assert.EqualError(t, err, "version was not defined in testdata/missing-version/porter.yaml") 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /mage/examples/testdata/missing-name/porter.yaml: -------------------------------------------------------------------------------- 1 | version: 0.1.0 2 | registry: myregistry 3 | -------------------------------------------------------------------------------- /mage/examples/testdata/missing-registry/porter.yaml: -------------------------------------------------------------------------------- 1 | name: missing-registry 2 | version: 0.1.0 3 | reference: localhost:5000/missing-registry 4 | -------------------------------------------------------------------------------- /mage/examples/testdata/missing-version/porter.yaml: -------------------------------------------------------------------------------- 1 | name: missing-registry 2 | registry: myregistry 3 | -------------------------------------------------------------------------------- /mage/setup/mixins.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "get.porter.sh/magefiles/porter" 5 | "github.com/magefile/mage/mg" 6 | "golang.org/x/sync/errgroup" 7 | ) 8 | 9 | // InstallMixins used by the example bundles. 10 | // If you add an example that uses a new mixin, update this function to install it. 11 | func InstallMixins() error { 12 | mg.SerialDeps(porter.UseBinForPorterHome, EnsurePorter) 13 | 14 | mixins := []porter.InstallMixinOptions{ 15 | {Name: "arm"}, 16 | {Name: "az"}, 17 | {Name: "docker"}, 18 | {Name: "docker-compose"}, 19 | {Name: "exec"}, 20 | {Name: "helm3", Feed: "https://mchorfa.github.io/porter-helm3/atom.xml", Version: "v1.0.1"}, 21 | {Name: "kubernetes"}, 22 | {Name: "terraform"}, 23 | } 24 | var errG errgroup.Group 25 | for _, mixin := range mixins { 26 | mixin := mixin 27 | errG.Go(func() error { 28 | return porter.EnsureMixin(mixin) 29 | }) 30 | } 31 | return errG.Wait() 32 | } 33 | 34 | func EnsurePorter() { 35 | porter.EnsurePorter() 36 | } 37 | -------------------------------------------------------------------------------- /magefile.go: -------------------------------------------------------------------------------- 1 | //go:build mage 2 | 3 | // This is a magefile, and is a "makefile for go". 4 | // See https://magefile.org/ 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "get.porter.sh/example-bundles/mage/examples" 14 | "get.porter.sh/example-bundles/mage/setup" 15 | "get.porter.sh/magefiles/git" 16 | "get.porter.sh/magefiles/porter" 17 | "github.com/carolynvs/magex/mgx" 18 | 19 | // mage:import 20 | _ "get.porter.sh/magefiles/ci" 21 | "github.com/carolynvs/magex/shx" 22 | "github.com/hashicorp/go-multierror" 23 | "github.com/magefile/mage/mg" 24 | ) 25 | 26 | func Build() { 27 | mg.Deps(BuildExamples) 28 | } 29 | 30 | func Test() error { 31 | return shx.RunV("go", "test", "./...") 32 | } 33 | 34 | // BuildExamples builds every example bundle 35 | func BuildExamples() error { 36 | var bigErr *multierror.Error 37 | names, err := examples.List(".") 38 | if err != nil { 39 | return err 40 | } 41 | 42 | for _, bundleName := range names { 43 | if err := BuildExample(bundleName); err != nil { 44 | // Keep trying to build all the bundles, don't stop on the first one 45 | bigErr = multierror.Append(bigErr, fmt.Errorf("error building bundle %s: %w", bundleName, err)) 46 | continue 47 | } 48 | } 49 | 50 | return bigErr.ErrorOrNil() 51 | } 52 | 53 | // BuildExample builds the specified example bundle 54 | func BuildExample(name string) error { 55 | mg.SerialDeps(setup.InstallMixins) 56 | 57 | fmt.Println("\n==========================") 58 | fmt.Printf("Building example bundle: %s\n", name) 59 | 60 | if customBuildFlags, err := os.ReadFile(filepath.Join(name, "build-args.txt")); err == nil { 61 | customBuildArgs := strings.Split(string(customBuildFlags), " ") 62 | buildArgs := append([]string{"build"}, customBuildArgs...) 63 | return shx.Command("porter", buildArgs...). 64 | CollapseArgs().In(name).RunV() 65 | } 66 | // Always build for amd64 even if on an arm host 67 | // This is a bit of a hack until we have multi-arch support 68 | return shx.Command("porter", "build"). 69 | In(name).Env("DOCKER_DEFAULT_PLATFORM=linux/amd64").RunV() 70 | } 71 | 72 | func Publish() { 73 | mg.Deps(PublishExamples) 74 | } 75 | 76 | // PublishExamples publishes every example bundle 77 | func PublishExamples() error { 78 | var bigErr *multierror.Error 79 | names, err := examples.List(".") 80 | if err != nil { 81 | return err 82 | } 83 | 84 | for _, bundleName := range names { 85 | if err := PublishExample(bundleName); err != nil { 86 | // Keep trying to publish all the bundles, don't stop on the first one 87 | bigErr = multierror.Append(bigErr, fmt.Errorf("error publishing bundle %s: %w", bundleName, err)) 88 | continue 89 | } 90 | } 91 | 92 | return bigErr.ErrorOrNil() 93 | } 94 | 95 | // PublishExample publishes the specified example bundle 96 | func PublishExample(name string) error { 97 | mg.SerialDeps(porter.UseBinForPorterHome, porter.EnsurePorter) 98 | 99 | fmt.Println("\n==========================") 100 | 101 | registryFlag := "" 102 | registry := os.Getenv("PORTER_REGISTRY") 103 | if registry != "" { 104 | registryFlag = "--registry=" + registry 105 | } 106 | 107 | // Check if the bundle already is published 108 | bundleRef, err := examples.GetBundleRef(name, registry) 109 | mgx.Must(err) 110 | 111 | // Do not overwrite an already published bundle 112 | // See https://github.com/getporter/porter/issues/2017 113 | if err := shx.RunS("porter", "explain", "-r", bundleRef); err == nil { 114 | fmt.Printf("Skipping publish for example bundle: %s. The bundle is already published to %s.\n", name, bundleRef) 115 | return nil 116 | } 117 | 118 | fmt.Printf("Publishing example bundle: %s\n", name) 119 | return shx.Command("porter", "publish", registryFlag).CollapseArgs().In(name).RunV() 120 | } 121 | 122 | // SetupDCO configures your git repository to automatically sign your commits 123 | // to comply with our DCO 124 | func SetupDCO() error { 125 | return git.SetupDCO() 126 | } 127 | -------------------------------------------------------------------------------- /otel-jaeger/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !.cnab/ 3 | !config.yaml 4 | !docker-compose.yaml 5 | !helpers.sh 6 | !porter.yaml 7 | -------------------------------------------------------------------------------- /otel-jaeger/.env: -------------------------------------------------------------------------------- 1 | JAEGER_UI_PORT=16686 2 | JAEGER_COLLECTOR_PORT=14250 3 | OTEL_COLLECTOR_PORT=4317 4 | -------------------------------------------------------------------------------- /otel-jaeger/.gitignore: -------------------------------------------------------------------------------- 1 | .cnab/ 2 | -------------------------------------------------------------------------------- /otel-jaeger/README.md: -------------------------------------------------------------------------------- 1 | # Jaeger with OpenTelemetry 2 | 3 | Porter v1 has experimental support for OpenTelemetry and can [send logs and trace data][diagnostics]. 4 | This bundle runs containers for Jaeger and an OpenTelemetry collector on the current host and is intended for developers to use when working on Porter. 5 | It is not suitable for production installations. 6 | 7 | 🚨 This bundle requires access to the host, and `--allow-docker-host-access` must be set when running the bundle. 8 | 9 | ## Try it out 10 | 11 | After installing this bundle, use the following settings in your ~/.porter/config.toml file to collect trace data: 12 | 13 | ```toml 14 | experimental = ["structured-logs"] 15 | 16 | [telemetry] 17 | enabled = true 18 | protocol = "grpc" 19 | insecure = true 20 | ``` 21 | 22 | Next, run a porter command, such as `porter list`, to generate trace data which should be sent immediately to Jaeger. 23 | You can view the collected data by opening your browser to http://localhost:16686. 24 | 25 | ![screenshot of the jaeger dashboard, note that the service drop down had porter selected](dashboard.png) 26 | 27 | ## Parameters 28 | 29 | Use `porter explain --reference ghcr.io/getporter/examples/otel-jaeger:v0.1.0` for the most up-to-date explanation of how to run this bundle. 30 | 31 | | Name | Default | Description | 32 | | ---- | ------- | ----------- | 33 | | jaeger-ui-port | 16686 | Port of the Jaeger website | 34 | | otel-collector-port | 4317 | Port of the OpenTelemetry collector (gRPC) | 35 | 36 | If you change the otel-collector-port, then you need to configure telemetry.endpoint. For example, if you set the port to 5000, set telemetry.endpoint to 127.0.0.1:5000. 37 | 38 | ## Install 39 | 40 | Note that each time you install or upgrade the bundle, all existing collected data is lost. 41 | 42 | ``` 43 | porter install --reference ghcr.io/getporter/examples/otel-jaeger:v0.1.0 --allow-docker-host-access 44 | ``` 45 | 46 | [diagnostics]: https://porter.sh/administrators/diagnostics/ 47 | -------------------------------------------------------------------------------- /otel-jaeger/config.yaml: -------------------------------------------------------------------------------- 1 | receivers: 2 | otlp: 3 | protocols: 4 | grpc: 5 | 6 | exporters: 7 | logging: 8 | 9 | jaeger: 10 | endpoint: jaeger-all-in-one:14250 11 | tls: 12 | insecure: true 13 | 14 | processors: 15 | batch: 16 | 17 | service: 18 | pipelines: 19 | traces: 20 | receivers: [otlp] 21 | processors: [batch] 22 | exporters: [logging, jaeger] 23 | -------------------------------------------------------------------------------- /otel-jaeger/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getporter/examples/3380eff019db6cd1cdb118abeab9b5da366d5720/otel-jaeger/dashboard.png -------------------------------------------------------------------------------- /otel-jaeger/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | 4 | # Jaeger 5 | # If we were cool, we would use the agent, etc directly and configure 6 | # persistent storage. But this bundle is really designed for local dev. 7 | jaeger-all-in-one: 8 | image: "jaegertracing/all-in-one:1.49" 9 | ports: 10 | - "${JAEGER_UI_PORT}:16686" 11 | - "14250" 12 | restart: always 13 | 14 | # Collector 15 | otel-collector: 16 | image: "otel/opentelemetry-collector-contrib:0.84.0" 17 | volumes: 18 | - otel-jaeger-config:/etc/otel 19 | ports: 20 | - "${OTEL_COLLECTOR_PORT}:4317" 21 | depends_on: 22 | - jaeger-all-in-one 23 | restart: always 24 | 25 | volumes: 26 | otel-jaeger-config: 27 | external: true 28 | -------------------------------------------------------------------------------- /otel-jaeger/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | set-config() { 5 | echo "Creating otel-jaeger-config volume" 6 | docker volume create otel-jaeger-config || true 7 | 8 | echo "Copying otel config into volume..." 9 | docker rm otel-jaeger-helper 2&> /dev/null || true 10 | docker run -v otel-jaeger-config:/config --name otel-jaeger-helper busybox true 11 | trap "docker rm otel-jaeger-helper" EXIT 12 | 13 | docker cp config.yaml otel-jaeger-helper:/config 14 | } 15 | 16 | remove-config() { 17 | docker volume rm otel-jaeger-config 18 | } 19 | 20 | # Call the requested function and pass the arguments as-is 21 | "$@" -------------------------------------------------------------------------------- /otel-jaeger/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/otel-jaeger 3 | version: 0.1.1 4 | description: "Runs Jaeger with an OpenTelemetry collector" 5 | registry: ghcr.io/getporter 6 | 7 | parameters: 8 | - name: jaeger-ui-port 9 | description: Port of the Jaeger website 10 | type: integer 11 | default: 16686 12 | - name: otel-collector-port 13 | description: Port of the OpenTelemetry collector (gRPC) 14 | type: integer 15 | default: 4317 16 | 17 | required: 18 | - docker 19 | 20 | mixins: 21 | - docker 22 | - docker-compose 23 | - exec 24 | 25 | install: 26 | - exec: 27 | command: echo 28 | arguments: 29 | - "hello" 30 | - exec: 31 | description: "Create OpenTelemetry configuration" 32 | command: ./helpers.sh 33 | arguments: 34 | - set-config 35 | - docker-compose: 36 | description: "Start Jaeger and OpenTelemetry containers" 37 | arguments: 38 | - up 39 | - --detach 40 | - --force-recreate 41 | 42 | upgrade: 43 | - exec: 44 | description: "Update OpenTelemetry configuration" 45 | command: ./helpers.sh 46 | arguments: 47 | - set-config 48 | - docker-compose: 49 | description: "Update Jaeger and OpenTelemetry containers" 50 | arguments: 51 | - up 52 | - --detach 53 | - --force-recreate 54 | 55 | uninstall: 56 | - docker-compose: 57 | description: "Stop Jaeger and OpenTelemetry containers" 58 | arguments: 59 | - down 60 | - -v 61 | - exec: 62 | description: "Remove configuration volume" 63 | command: ./helpers.sh 64 | arguments: 65 | - remove-config 66 | -------------------------------------------------------------------------------- /plugins-tutorial/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | install() { 5 | echo Using Magic Password: $1 6 | } 7 | 8 | upgrade() { 9 | echo World is now at 2.0 10 | } 11 | 12 | uninstall() { 13 | echo Goodbye World 14 | } 15 | 16 | # Call the requested function and pass the arguments as-is 17 | "$@" 18 | -------------------------------------------------------------------------------- /plugins-tutorial/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/plugins-tutorial 3 | version: 0.2.0 4 | description: -| 5 | Example of porter resolving credentials from a secrets store using a plugin. 6 | This bundle is a companion for the plugin tutorial at https://porter.sh/plugins/tutorial/. 7 | registry: ghcr.io/getporter 8 | 9 | credentials: 10 | - name: password 11 | description: -| 12 | Password for installing the world. We recommend getting this 13 | from a secret store. 14 | env: PASSWORD 15 | type: string 16 | applyTo: 17 | - install 18 | 19 | mixins: 20 | - exec 21 | 22 | install: 23 | - exec: 24 | description: "Install World" 25 | command: ./helpers.sh 26 | arguments: 27 | - "install {{ bundle.credentials.password }}" 28 | 29 | upgrade: 30 | - exec: 31 | description: "Upgrade to World 2.0" 32 | command: ./helpers.sh 33 | arguments: 34 | - upgrade 35 | 36 | uninstall: 37 | - exec: 38 | description: "Uninstall World" 39 | command: ./helpers.sh 40 | arguments: 41 | - uninstall 42 | -------------------------------------------------------------------------------- /porter-discourse/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl 5 | -------------------------------------------------------------------------------- /porter-discourse/.gitignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .cnab/ 3 | -------------------------------------------------------------------------------- /porter-discourse/Dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | FROM debian:stable-slim 2 | 3 | ARG BUNDLE_DIR 4 | 5 | RUN apt-get update && apt-get install -y ca-certificates 6 | 7 | # This is a template Dockerfile for the bundle's invocation image 8 | # You can customize it to use different base images, install tools and copy configuration files. 9 | # 10 | # Porter will use it as a template and append lines to it for the mixins 11 | # and to set the CMD appropriately for the CNAB specification. 12 | # 13 | # Add the following line to porter.yaml to instruct Porter to use this template 14 | # dockerfile: Dockerfile.tmpl 15 | 16 | # You can control where the mixin's Dockerfile lines are inserted into this file by moving "# PORTER_MIXINS" line 17 | # another location in this file. If you remove that line, the mixins generated content is appended to this file. 18 | # PORTER_MIXINS 19 | 20 | # Use the BUNDLE_DIR build argument to copy files into the bundle 21 | COPY . ${BUNDLE_DIR} 22 | -------------------------------------------------------------------------------- /porter-discourse/README.md: -------------------------------------------------------------------------------- 1 | # Discourse Example Bundle 2 | This example is a work in progress and not finished. 3 | 4 | ## Why Discourse? 5 | [Discourse](https://www.discourse.org/about) is the perfect example bundle because it will illustrate the need for bundling your application and using Porter. Discourse is a platform for discussion with many exciting [features](https://www.discourse.org/features). 6 | 7 | The installation and setup process for Discourse is deceptively simple at first glance. However, it is actually extremely complicated. Although there is a standalone docker image that you can run, there is still a lot of infrastructure that needs to be configured, especially if you wish to use Discourse in production and want to take advantage of all the features that are possible. 8 | 9 | Because of this, you cannot just run the docker image and do a simple install. You will have to go through the complex process in order to set up the infrastructure and customize your Discourse. 10 | 11 | In order to install, first, you need to create a cloud server and access it. Then, you need to install docker and install discourse. To get email working, you will need to set up a mail server and get the credentials. You also need a domain name. 12 | 13 | Then, you will launch a discourse set up tool and answer questions about your hostname, email address, server address, etc. After Discourse is up and running, if an update needs to be made or maintenance is needed, another process is required. 14 | 15 | Additionally, if you want more features such as single-sign-on, plugins, or encryption, those need to be configured as well. Because of all these complex steps that are needed to install Discourse and upgrade it, putting Discourse in a bundle would greatly simplify the process. 16 | 17 | Bundling Discourse would take away the need for individuals installing it to read through all the installation instructions. Users would simply need to enter their credentials and any parameters and Porter would do the work for them. Discourse would be installed with just one command. 18 | 19 | ## What bundle should do and look like (parameters and credentials etc) 20 | install: 21 | * cloud server (DigitalOcean, Azure, etc) 22 | - need credentials 23 | * cloud storage for user uploads, pictures (Amazon S3, Azure Blob Storage, etc) 24 | - can set up backups 25 | - need credentials 26 | * email (Mailgun, SendGrid, Mailjet) 27 | - can configure reply via email 28 | - need credentials and email as parameter 29 | * domain name 30 | - hostname parameter 31 | * ssl certificate (Let’s Encrypt free certificate) 32 | - need certificate and key 33 | - have to configure NGINX and a docker container 34 | * Virtual Machine 35 | - need credentials 36 | * postgres database 37 | - need parameters for username, password, database name 38 | * configure SSO 39 | - enable_sso parameter must be enabled 40 | - sso_url: the offsite URL users will be sent to when attempting to log on 41 | - sso_secret: a secret string used to hash SSO payloads 42 | * login via Google, Twitter, GitHub, Facebook 43 | * install plugins 44 | - need plugin's git clone url 45 | - add it to app.yml 46 | - rebuild the container 47 | * multisite configuration with docker 48 | - if you want to host multiple Discourse sites on the same server 49 | * set up webhooks 50 | * enable a CDN 51 | - origin address 52 | - CNAME 53 | - CDN URL 54 | - need to edit DNS map to map CNAME to CDN URL 55 | 56 | upgrade: 57 | - have exec run a script that will do the upgrade (probably run rebuild) 58 | 59 | potential custom actions from launcher: 60 | * start: Start/initialize a container 61 | * stop: Stop a running container 62 | * restart: Restart a container 63 | * destroy: Stop and remove a container 64 | * enter: Use nsenter to get a shell into a container 65 | * logs: View the Docker logs for a container 66 | * bootstrap: Bootstrap a container for the config based on a template 67 | * rebuild: Rebuild a container (destroy old, bootstrap, start new) 68 | * cleanup: Remove all containers that have stopped for > 24 hours 69 | 70 | uninstall: 71 | * delete azure storage account 72 | * delete azure storage container 73 | * delete azure vm 74 | * delete postgres database 75 | -------------------------------------------------------------------------------- /porter-discourse/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | install() { 5 | echo Hello World 6 | } 7 | 8 | upgrade() { 9 | echo World 2.0 10 | } 11 | 12 | uninstall() { 13 | echo Goodbye World 14 | } 15 | 16 | # Call the requested function and pass the arguments as-is 17 | "$@" 18 | -------------------------------------------------------------------------------- /porter-discourse/porter-discourse.yaml: -------------------------------------------------------------------------------- 1 | # This is the configuration for Porter 2 | # You must define steps for each action, but the rest is optional 3 | # See https://porter.sh/author-bundles for documentation on how to configure your bundle 4 | # Uncomment out the sections below to take full advantage of what Porter can do! 5 | 6 | schemaVersion: 1.0.0-alpha.1 7 | name: discourse-azure 8 | version: 0.1.0 9 | description: "A Porter bundle for Discourse" 10 | # TODO: update the registry to your own, e.g. myregistry 11 | registry: ghcr.io/getporter 12 | 13 | # Uncomment the line below to use a template Dockerfile for your invocation image 14 | #dockerfile: Dockerfile.tmpl 15 | 16 | mixins: 17 | - az 18 | - exec 19 | 20 | credentials: 21 | - name: sp_client_id 22 | env: AZURE_SP_CLIENT_ID 23 | 24 | - name: sp_password 25 | env: AZURE_SP_PASSWORD 26 | 27 | - name: tenant 28 | env: AZURE_TENANT_ID 29 | 30 | - name: client_secret 31 | env: AZURE_CLIENT_SECRET 32 | 33 | - name: client_id 34 | env: AZURE_CLIENT_ID 35 | 36 | - name: client_secret 37 | env: AZURE_CLIENT_SECRET 38 | 39 | - name: storage_key 40 | env: AZURE_STORAGE_KEY 41 | 42 | - name: storage_connection_string 43 | env: AZURE_STORAGE_CONNECTION_STRING 44 | 45 | parameters: 46 | - name: location 47 | type: string 48 | default: "EastUS" 49 | 50 | - name: resource_group_name 51 | type: string 52 | default: "porter-discourse" 53 | 54 | - name: storage_account_name 55 | type: string 56 | default: "porterstorage" 57 | 58 | - name: storage_container_name 59 | type: string 60 | default: "porterdiscourse" 61 | 62 | - name: database-name 63 | type: string 64 | default: "porter-discourse" 65 | 66 | - name: discourse_hostname 67 | type: string 68 | default: "" 69 | 70 | - name: admin_email 71 | type: string 72 | 73 | - name: SMTP_server_address 74 | type: string 75 | 76 | - name: SMTP_port 77 | type: string 78 | 79 | - name: SMTP_user_name 80 | type: string 81 | 82 | - name: SMTP_password 83 | type: string 84 | 85 | install: 86 | - az: 87 | description: "Azure ClI login" 88 | arguments: 89 | - login 90 | flags: 91 | service-principal: 92 | username: "{{ bundle.credentials.sp_client_id}}" 93 | password: "{{ bundle.credentials.sp_password}}" 94 | tenant: "{{ bundle.credentials.tenant}}" 95 | - az: 96 | description: "Create Azure storage account" 97 | arguments: 98 | - storage 99 | - account 100 | - create 101 | flags: 102 | resource-group: "{{ bundle.parameters.resource_group_name }}" 103 | name: "{{ bundle.parameters.storage_account_name }}" 104 | location: "{{ bundle.parameters.location }}" 105 | - az: 106 | description: "Create Azure storage container" 107 | arguments: 108 | - storage 109 | - container 110 | - create 111 | flags: 112 | resource-group: "{{ bundle.parameters.resource_group_name }}" 113 | name: "{{ bundle.parameters.storage_container_name }}" 114 | account-name: "{{ bundle.parameters.storage_account_name }}" 115 | - az: 116 | description: "Create Postgres database on Azure" 117 | arguments: 118 | - postgres 119 | - db 120 | - create 121 | flags: 122 | resource-group: "{{ bundle.parameters.resource_group_name }}" 123 | name: "{{ bundle.parameters.database-name }}" 124 | server-name: myServer 125 | location: "{{ bundle.parameters.location }}" 126 | # TODO: define an output for the database connection string 127 | - az: 128 | description: "Create Azure VM" 129 | arguments: 130 | - vm 131 | - create 132 | flags: 133 | resource-group: "{{ bundle.parameters.resource_group_name }}" 134 | name: myVM 135 | image: UbuntuLTS 136 | location: "{{ bundle.parameters.location }}" 137 | # TODO: define an output for the IP address of the VM 138 | # TODO: define an output for the root password for the VM 139 | - exec: 140 | description: "Run script to install Discourse" 141 | command: ./installDiscourse.sh 142 | # TODO: pass in arguments from outputs of previous steps - connection string to database, the root password for VM, and the IP address for the VM 143 | # TODO: pass in host name for forum (ex. forum.porter.sh) 144 | # bash script would automate the process of creating the yaml script, injecting the outputs into the script, and kicking off the setup 145 | 146 | upgrade: 147 | - exec: 148 | description: "Run script to upgrade discourse" 149 | command: ./upgradeDiscourse.sh 150 | # bash script will probably run rebuild from launcher 151 | 152 | uninstall: 153 | - az: 154 | description: "Delete Azure storage" 155 | arguments: 156 | - storage 157 | - account 158 | - delete 159 | flags: 160 | resource-group: "{{ bundle.parameters.resource_group_name }}" 161 | name: "{{ bundle.parameters.storage_account_name }}" 162 | - az: 163 | description: "Delete Azure storage container" 164 | arguments: 165 | - storage 166 | - container 167 | - delete 168 | flags: 169 | name: "{{ bundle.parameters.storage_container_name }}" 170 | - az: 171 | description: "Delete VM" 172 | arguments: 173 | - vm 174 | - delete 175 | flags: 176 | resource-group: "{{ bundle.parameters.resource_group_name }}" 177 | name: myVM 178 | - az: 179 | description: "Delete Postgres database" 180 | arguments: 181 | - postgres 182 | - db 183 | - delete 184 | flags: 185 | resource-group: "{{ bundle.parameters.resource_group_name }}" 186 | name: "{{ bundle.parameters.database-name }}" 187 | server-name: myServer -------------------------------------------------------------------------------- /private-assets/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl 5 | secrets/ 6 | build-args.txt 7 | -------------------------------------------------------------------------------- /private-assets/.gitignore: -------------------------------------------------------------------------------- 1 | .cnab/ 2 | -------------------------------------------------------------------------------- /private-assets/README.md: -------------------------------------------------------------------------------- 1 | # Bundle with Private Assets 2 | 3 | Sometimes you need to include assets from secured locations, such as a private repository in your bundle. 4 | You can use the \--secret flag to pass secrets into the bundle when it is built. 5 | 6 | ## Try it out 7 | 1. Edit secrets/token and replace the contents with a [GitHub Personal Access Token](https://github.com/settings/tokens). 8 | The permissions do not matter for this sample bundle. 9 | There should not be a newline at the end of the file. 10 | 11 | 1. Build the bundle and pass the secret into the bundle with \--secret 12 | ``` 13 | porter build --secret id=token,src=secrets/token 14 | ``` 15 | 16 | 1. Install the bundle to see the private assets embedded in the bundle 17 | ``` 18 | $ porter install example-private-assets --reference ghcr.io/getporter/examples/private-assets:v0.1.0 19 | __________________________ 20 | < yarr, I'm a secret whale > 21 | -------------------------- 22 | \ 23 | \ 24 | \ 25 | ## . 26 | ## ## ## == 27 | ## ## ## ## === 28 | /""""""""""""""""___/ === 29 | ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~ 30 | \______ o __/ 31 | \ \ __/ 32 | \____\______/ 33 | ``` 34 | -------------------------------------------------------------------------------- /private-assets/build-args.txt: -------------------------------------------------------------------------------- 1 | --secret id=token,src=secrets/token 2 | -------------------------------------------------------------------------------- /private-assets/check-secrets.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | if [[ ! -f "/run/secrets/token" ]]; then 5 | echo "You forgot to use --secret id=token,src=secrets/token" 6 | exit 1 7 | fi 8 | -------------------------------------------------------------------------------- /private-assets/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | install() { 5 | echo Hello World 6 | } 7 | 8 | upgrade() { 9 | echo World 2.0 10 | } 11 | 12 | uninstall() { 13 | echo Goodbye World 14 | } 15 | 16 | # Call the requested function and pass the arguments as-is 17 | "$@" 18 | -------------------------------------------------------------------------------- /private-assets/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/private-assets 3 | version: 0.1.0 4 | description: "Example bundle that contains private assets and prints it when run" 5 | registry: ghcr.io/getporter/ 6 | dockerfile: template.Dockerfile 7 | 8 | mixins: 9 | - exec 10 | 11 | install: 12 | - exec: 13 | command: cat 14 | arguments: 15 | - /secret 16 | 17 | upgrade: 18 | - exec: 19 | command: cat 20 | arguments: 21 | - /secret 22 | 23 | uninstall: 24 | - exec: 25 | command: cat 26 | arguments: 27 | - /secret 28 | -------------------------------------------------------------------------------- /private-assets/secrets/token: -------------------------------------------------------------------------------- 1 | REPLACE_WITH_YOUR_GITHUB_TOKEN -------------------------------------------------------------------------------- /private-assets/template.Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile-upstream:1.4.0 2 | FROM debian:stable-slim 3 | 4 | # PORTER_INIT 5 | 6 | RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache 7 | RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \ 8 | apt-get update && apt-get install -y ca-certificates curl 9 | 10 | # PORTER_MIXINS 11 | 12 | # Use the BUNDLE_DIR build argument to copy files into the bundle's working directory 13 | COPY --link . ${BUNDLE_DIR} 14 | 15 | # Check the secret was passed to the build command 16 | RUN --mount=type=secret,id=token /cnab/app/check-secrets.sh 17 | 18 | # Use the injected secrets to build private assets into the bundle 19 | RUN --mount=type=secret,id=token curl -O https://$(cat /run/secrets/token)@gist.githubusercontent.com/carolynvs/860a0d26de3af1468d290a075a91aac9/raw/c53223acd284830e8f541cf35eba94dde0ddf75d/secret 20 | -------------------------------------------------------------------------------- /sensitive-data/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl 5 | -------------------------------------------------------------------------------- /sensitive-data/.gitignore: -------------------------------------------------------------------------------- 1 | .cnab/ 2 | -------------------------------------------------------------------------------- /sensitive-data/README.md: -------------------------------------------------------------------------------- 1 | # Bundle with sensitive data 2 | 3 | This bundle demonstrates how porter works with bundle that contains sensitive 4 | data. 5 | 6 | It requires users to set up a secret store in their porter configuration 7 | file in order to work with this bundle. 8 | 9 | ## Try it out 10 | 11 | Follow the steps in the [Upgrade your plugins to securely store sensitive data](https://porter.sh/blog/persist-sensitive-data-safely/) documentation 12 | to setup [filesystem](https://porter.sh/plugins/filesystem/) plugin. 13 | After setting up the secret plugin, run to install the bundle: 14 | 15 | ``` 16 | porter install --reference ghcr.io/getporter/examples/sensitive-data:v0.1.0 --param password=test 17 | ``` 18 | -------------------------------------------------------------------------------- /sensitive-data/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | name=$(cat /cnab/app/foo/name.txt) 5 | 6 | install() { 7 | echo "Hello, installing $name with password: $1" 8 | } 9 | 10 | upgrade() { 11 | echo "Hello, upgrading $name" 12 | } 13 | 14 | uninstall() { 15 | echo "Goodbye, $name" 16 | } 17 | 18 | # Call the requested function and pass the arguments as-is 19 | "$@" 20 | -------------------------------------------------------------------------------- /sensitive-data/porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0-alpha.1 2 | name: examples/sensitive-data 3 | version: 0.1.0 4 | description: "An example bundle that generates sensitive data" 5 | registry: ghcr.io/getporter 6 | 7 | parameters: 8 | - name: name 9 | type: string 10 | default: example-bundle 11 | path: /cnab/app/foo/name.txt 12 | source: 13 | output: name 14 | - name: password 15 | type: string 16 | sensitive: true 17 | 18 | outputs: 19 | - name: name 20 | path: /cnab/app/foo/name.txt 21 | sensitive: true 22 | 23 | mixins: 24 | - exec 25 | 26 | install: 27 | - exec: 28 | description: "Install Hello World" 29 | command: ./helpers.sh 30 | arguments: 31 | - install 32 | - "{{ bundle.parameters.password }}" 33 | 34 | upgrade: 35 | - exec: 36 | description: "World 2.0" 37 | command: ./helpers.sh 38 | arguments: 39 | - upgrade 40 | 41 | uninstall: 42 | - exec: 43 | description: "Uninstall Hello World" 44 | command: ./helpers.sh 45 | arguments: 46 | - uninstall 47 | 48 | --------------------------------------------------------------------------------