├── CODE_OF_CONDUCT.md ├── README.md ├── bash-vars-required ├── README.md ├── bash-required-variables.gif ├── req-colon-question.sh └── req-set-u.sh ├── cf-service-key ├── README.md └── db-contents.sh ├── concourse-find-resource ├── README.md └── find-resource.sh ├── github-repos-author ├── .gitignore ├── author.sh ├── authors.sh ├── create-authors-logs.sh ├── highest-authors.sh └── members.sh ├── safe-gen ├── README.md ├── metadata.yml ├── safely-generate-ssh-keys.gif └── tutorial.sh ├── spruce-diff ├── README.md ├── manifest.yml ├── prod-env.yml ├── spruce-diff-differences.png ├── staging-env.yml └── tutorial.sh ├── spruce-json ├── README.md ├── metadata.yml ├── spruce-json.png ├── tutorial.sh └── values.yml ├── spruce-merge-array-items ├── README.md ├── metadata.yml └── tutorial.sh ├── spruce-merge-docker-compose ├── README.md ├── docker-compose.yml ├── redis-v2.yml ├── redis-v3.yml └── spruce-merge-to-build-docker-compose-yml.gif └── spruce-merge-environments ├── README.md ├── manifest.yml ├── prod-env.yml ├── staging-env.yml └── tutorial.sh /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## 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, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | 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, pull requests, and other 42 | contributions that are not aligned to this Code of Conduct, or to ban 43 | temporarily or permanently any contributor for other behaviors that they deem 44 | inappropriate, 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 by contacting the project team at conduct@starkandwayne.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ### Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tiny Tutorials 2 | 3 | We work with lots of tools and they can be combined together in many interesting ways. 4 | 5 | Some tutorials are examples of ways Stark & Wayne has helped clients' become elegantly effective. Others are pull requests from you! 6 | 7 | Each subfolder includes a standalone tiny tutorial. Each tiny tutorial might also expand on, or lead to other tiny tutorials. 8 | 9 | ## Submitting tiny tutorials 10 | 11 | Please submit tiny tutorials as pull requests to the `master` branch. 12 | 13 | Include the tutorial in a `README.md` file. 14 | -------------------------------------------------------------------------------- /bash-vars-required/README.md: -------------------------------------------------------------------------------- 1 | # Required variables for bash scripts 2 | 3 | You can enforce that all variables must be specified with `set -u`, or you can discerningly use `${var1:?required}` to show errors. 4 | 5 | ![req](bash-required-variables.gif) 6 | -------------------------------------------------------------------------------- /bash-vars-required/bash-required-variables.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egen/tiny-tutorials/ce22d26c431c1f50b731c4521ee5c95cb6075498/bash-vars-required/bash-required-variables.gif -------------------------------------------------------------------------------- /bash-vars-required/req-colon-question.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "var1: ${var1:?needed config}" 4 | 5 | -------------------------------------------------------------------------------- /bash-vars-required/req-set-u.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -u 4 | echo "var1: ${var1}" 5 | -------------------------------------------------------------------------------- /cf-service-key/README.md: -------------------------------------------------------------------------------- 1 | # Interact with PostgreSQL via Service Keys 2 | 3 | Each Cloud Foundry service binding is for an application's specific use. If an app developer wants to directly interact with a service instance, such as a PostgreSQL database, then use Service Keys. 4 | 5 | For example: 6 | 7 | ``` 8 | cf create-service dingo-postgresql cluster mydb 9 | cf create-service-key mydb mydb-key 10 | cf service-key mydb mydb-key 11 | ``` 12 | 13 | For convenience, there is a `./db-contents.sh` script that does all of this and passes any arguments thru to `psql`: 14 | 15 | ``` 16 | ./db-contents.sh ghost-pg 17 | Arguments provided will be passed to 'psql'. Defaulting to "-c '\dt;' 18 | List of relations 19 | Schema | Name | Type | Owner 20 | --------+------------------------+-------+---------------- 21 | public | accesstokens | table | dvw7DJgqzFBJC8 22 | public | app_fields | table | dvw7DJgqzFBJC8 23 | public | app_settings | table | dvw7DJgqzFBJC8 24 | public | apps | table | dvw7DJgqzFBJC8 25 | public | client_trusted_domains | table | dvw7DJgqzFBJC8 26 | public | clients | table | dvw7DJgqzFBJC8 27 | ... 28 | public | users | table | dvw7DJgqzFBJC8 29 | (19 rows) 30 | ``` 31 | 32 | Or pass arguments: 33 | 34 | ``` 35 | ./db-contents.sh ghost-pg -c "select name from users;" 36 | ``` 37 | -------------------------------------------------------------------------------- /cf-service-key/db-contents.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | service_instance_name=$1 4 | 5 | if [[ ! -f ~/.cf/config.json ]]; then 6 | >&2 echo "Run 'cf login' to target & login to a Cloud Foundry" 7 | exit 1 8 | fi 9 | 10 | space_guid=$(cat ~/.cf/config.json | jq -r .SpaceFields.GUID) 11 | if [[ "${space_guid}" == "null" ]]; then 12 | >&2 echo "Run 'cf target -s ' to target a space" 13 | exit 1 14 | fi 15 | 16 | if [[ -z $service_instance_name ]]; then 17 | >&2 echo "USAGE: ./db-contents.sh " 18 | cf services 19 | exit 1 20 | fi 21 | 22 | shift 23 | 24 | 25 | service_instance=$(cf curl /v2/spaces/${space_guid}/service_instances\?q=name:$service_instance_name) 26 | if [[ "$(echo $service_instance | jq -r .total_results)" == "0" ]]; then 27 | >&2 echo "No service instance named '$service_instance'" 28 | >&2 echo "USAGE: ./db-contents.sh " 29 | exit 1 30 | fi 31 | 32 | service_instance_keys_url=$(echo $service_instance | jq -r ".resources[0].entity.service_keys_url") 33 | service_instance_keys=$(cf curl $service_instance_keys_url) 34 | if [[ "$(echo $service_instance_keys | jq -r .total_results)" == "0" ]]; then 35 | >&2 echo "No existing service key for '$service_instance_name'. Creating..." 36 | service_instance_guid=$(echo $service_instance | jq -r ".resources[0].metadata.guid") 37 | service_key=$(cf curl -X POST /v2/service_keys -d "{\"service_instance_guid\":\"${service_instance_guid}\",\"name\":\"${service_instance_name}-key\"}") 38 | else 39 | service_key=$(echo $service_instance_keys | jq -r ".resources[0]") 40 | fi 41 | 42 | uri=$(echo $service_instance_keys | jq -r ".resources[0].entity.credentials.uri") 43 | if [[ "${url:-X}" == "null" ]]; then 44 | >&2 echo "Service '${service_instance_name}' has no 'uri' credential" 45 | exit 1 46 | fi 47 | 48 | uri=postgres://postgres:Tof2gNVZMz6Dun@52.202.142.106:33007/postgres 49 | if [[ "${1:-X}" == "X" ]]; then 50 | psql $uri -c '\dt;' 51 | >&2 echo "Arguments provided will be passed to 'psql'. Defaulting to \"-c '\dt;'" 52 | else 53 | psql $uri "$@" 54 | fi 55 | -------------------------------------------------------------------------------- /concourse-find-resource/README.md: -------------------------------------------------------------------------------- 1 | # Concourse - find a resource in a pipeline 2 | 3 | At [Stark & Wayne](https://starkandwayne.com) we have over 85 https://concourse.ci pipelines running out company. 4 | 5 | One of them was pushing out `docker:///starkandwayne/concourse` but I didn't know which one. 6 | 7 | `find-resource.sh` helped find it. 8 | 9 | ``` 10 | ./find-resource.sh starkandwayne/concourse 11 | ``` 12 | 13 | This command searched thru each pipeline looking for `docker-image` resources with the repository `starkandwayne/concourse`: 14 | 15 | ``` 16 | Looking concourse-tutorial... 17 | Looking bosh-lites... 18 | Looking concourse-deployment... 19 | Looking cloudfoundry-utils... 20 | Looking covalence... 21 | Looking docker-images... 22 | { 23 | "name": "concourse:latest-rc @dockerhub", 24 | "source": { 25 | ... 26 | "repository": "starkandwayne/concourse", 27 | "tag": "latest-rc", 28 | "username": "starkandwaynebot" 29 | }, 30 | "type": "docker-image", 31 | "pipeline": "docker-images" 32 | } 33 | { 34 | "name": "concourse:latest @dockerhub", 35 | "source": { 36 | ... 37 | "repository": "starkandwayne/concourse", 38 | "tag": "latest", 39 | "username": "starkandwaynebot" 40 | }, 41 | "type": "docker-image", 42 | "pipeline": "docker-images" 43 | } 44 | ... 45 | ``` 46 | 47 | Found the pipeline: `docker-images` 48 | -------------------------------------------------------------------------------- /concourse-find-resource/find-resource.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | image_repository=$1 4 | shift 5 | : ${image_repository:?USAGE: resource.sh org/name} 6 | pipelines=($(fly -t sw pipelines | awk '{print $1}')) 7 | for pipeline in "${pipelines[@]}"; do 8 | echo "Looking $pipeline..." 9 | fly -t sw get-pipeline -p $pipeline | spruce json | \ 10 | jq -r --arg pipeline $pipeline --arg image $image_repository \ 11 | '.resources[] | select(.type == "docker-image") | select(.source.repository == $image) + {"pipeline": $pipeline}' 12 | done 13 | -------------------------------------------------------------------------------- /github-repos-author/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | -------------------------------------------------------------------------------- /github-repos-author/author.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | : ${GITHUB_TOKEN:?required} 4 | 5 | : ${repo:?required} 6 | links_regexp='Link: <(.*)>; rel="next", <(.*)>; rel="last"' 7 | last_commits=https://api.github.com/repos/$repo/commits 8 | commit_links=$(curl -s -H "Authorization: token $GITHUB_TOKEN" $last_commits -I 2>&1 | grep "^Link") 9 | if [[ $commit_links =~ $links_regexp ]]; then 10 | last_commits="${BASH_REMATCH[2]}" 11 | fi 12 | 13 | author=$(curl -s -H "Authorization: token $GITHUB_TOKEN" ${last_commits} | jq -r ".[-1].author.login // .[-1].commit.author.email") 14 | echo "$repo $author" 15 | -------------------------------------------------------------------------------- /github-repos-author/authors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # USAGE: 4 | # org=cloudfoundry-community ./authors.sh 5 | # org=cloudfoundry-community ./authors.sh | tee authors.log 6 | # 7 | # BONUS: count number of repos created by authors 8 | # $ cat authors.log | awk '{print $2}' | sort | uniq -c | sort 9 | # ... 10 | # 3 thomasmmitchell 11 | # 8 soutenniza 12 | # 11 geofffranks 13 | # 13 frodenas 14 | # 13 rkoster 15 | # 17 jhunt 16 | # 28 lnguyen 17 | # 67 drnic 18 | # 19 | # BONUS: count number of repos created by authors, filtered by members.sh 20 | # $ cat authors.log | egrep "($(cat members-starkandwayne.log | paste -sd "|" -))" | awk '{print $2}' | sort | uniq -c | sort 21 | # 1 cweibel 22 | # 1 fearoffish 23 | # 1 jrbudnack 24 | # 1 mrferris 25 | # 1 ramonskie 26 | # 2 bodymindarts 27 | # 2 dennisjbell 28 | # 2 givett 29 | # 2 johnlonganecker 30 | # 2 wayneeseguin 31 | # 3 thomasmmitchell 32 | # 8 soutenniza 33 | # 11 geofffranks 34 | # 13 frodenas 35 | # 13 rkoster 36 | # 17 jhunt 37 | # 28 lnguyen 38 | # 67 drnic 39 | # 40 | # BONUS: total repos initially created by a member of an organization 41 | # $ cat authors.log | egrep "($(cat members-starkandwayne.log | paste -sd "|" -))" | awk '{print $2}' | sort | uniq -c | sort | awk '{print $1}'| paste -sd+ - | bc 42 | # 175 43 | 44 | : ${GITHUB_TOKEN:?required} 45 | : ${org:?which organization to search thru for first author} 46 | 47 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 48 | links_regexp='Link: <(.*)>; rel="next", <(.*)>; rel="last"' 49 | 50 | function fetch_authors() { 51 | next_repos=$1 52 | repos=($(curl -s -H "Authorization: token $GITHUB_TOKEN" $next_repos | jq -r ".[].full_name")) 53 | for repo in "${repos[@]}"; do 54 | export repo 55 | $DIR/author.sh 56 | done 57 | } 58 | 59 | next_repos=https://api.github.com/orgs/$org/repos 60 | 61 | until [[ "${next_repos:-X}" == "X" ]]; do 62 | fetch_authors $next_repos 63 | 64 | next_repo_links=$(curl -s -H "Authorization: token $GITHUB_TOKEN" $next_repos -I 2>&1 | grep "^Link") 65 | if [[ $next_repo_links =~ $links_regexp ]]; then 66 | next_repos="${BASH_REMATCH[1]}" 67 | else 68 | next_repos= 69 | fi 70 | done 71 | -------------------------------------------------------------------------------- /github-repos-author/create-authors-logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for org in $@; do 4 | export org 5 | echo org: $org 6 | ./authors.sh | tee authors-$org.log 7 | 8 | echo members: $org 9 | ./members.sh | tee members-$org.log 10 | done 11 | -------------------------------------------------------------------------------- /github-repos-author/highest-authors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for org in $@; do 4 | export org 5 | echo org: $org 6 | echo 7 | 8 | if [[ ! -f authors-$org.log ]]; then 9 | echo "Missing authors-$org.log. Run create-authors-logs.sh first." 10 | exit 1 11 | fi 12 | cat authors-$org.log |egrep "($(cat members-$org.log | paste -sd "|" -))" | awk '{print $2}' | sort | uniq -c | sort 13 | done 14 | -------------------------------------------------------------------------------- /github-repos-author/members.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # USAGE: 4 | # org=cloudfoundry-community ./members.sh 5 | # export org=starkandwayne; ./members.sh | tee members-$org.log 6 | 7 | : ${GITHUB_TOKEN:?required} 8 | : ${org:?which organization to search thru for first author} 9 | 10 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 11 | links_regexp='Link: <(.*)>; rel="next", <(.*)>; rel="last"' 12 | 13 | function list_member_logins() { 14 | members=$1 15 | curl -s -H "Authorization: token $GITHUB_TOKEN" $members | jq -r ".[].login" 16 | } 17 | 18 | next_members=https://api.github.com/orgs/$org/members 19 | 20 | until [[ "${next_members:-X}" == "X" ]]; do 21 | list_member_logins $next_members 22 | next_members_links=$(curl -s -H "Authorization: token $GITHUB_TOKEN" $next_members -I 2>&1 | grep "^Link") 23 | if [[ $next_members_links =~ $links_regexp ]]; then 24 | next_members="${BASH_REMATCH[1]}" 25 | else 26 | next_members= 27 | fi 28 | done 29 | -------------------------------------------------------------------------------- /safe-gen/README.md: -------------------------------------------------------------------------------- 1 | # Safely generate passwords in Vault 2 | 3 | ![safe-gen](safely-generate-ssh-keys.gif) 4 | 5 | ``` 6 | vault_base=${vault_base:-secret/tiny-tutorials-demo} 7 | 8 | safe gen 20 $vault_base/staging/users/admin password 9 | safe get $vault_base/staging/users/admin 10 | ``` 11 | 12 | The output will be like: 13 | 14 | ``` 15 | --- # secret/tiny-tutorials-demo/staging/users/admin 16 | password: AAb1UB3ZaGl4ItE7d2jI 17 | 18 | ``` 19 | 20 | You can fetch any safe key (`password` in the example above) using the `:key` suffix: 21 | 22 | ``` 23 | safe get $vault_base/staging/users/admin:password 24 | ``` 25 | 26 | Will return just the value: 27 | 28 | ``` 29 | AAb1UB3ZaGl4ItE7d2jI 30 | ``` 31 | -------------------------------------------------------------------------------- /safe-gen/metadata.yml: -------------------------------------------------------------------------------- 1 | --- 2 | tutorials: 3 | - name: Safely generate passwords in Vault 4 | folder: safe-gen 5 | -------------------------------------------------------------------------------- /safe-gen/safely-generate-ssh-keys.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egen/tiny-tutorials/ce22d26c431c1f50b731c4521ee5c95cb6075498/safe-gen/safely-generate-ssh-keys.gif -------------------------------------------------------------------------------- /safe-gen/tutorial.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | vault_base=${vault_base:-secret/tiny-tutorials-demo} 6 | 7 | safe gen 20 $vault_base/staging/users/admin password 8 | safe get $vault_base/staging/users/admin 9 | safe get $vault_base/staging/users/admin:password 10 | -------------------------------------------------------------------------------- /spruce-diff/README.md: -------------------------------------------------------------------------------- 1 | # Show differences in YAML files 2 | 3 | Not all YAML files are the same. It can be difficult to to spot differences in very large YAML files. 4 | 5 | `spruce diff` makes this nice for you: 6 | 7 | ![diffs](spruce-diff-differences.png) 8 | 9 | ## Files with differences 10 | 11 | Try anyone of the following equivalent commands: 12 | 13 | ``` 14 | spruce diff spruce-diff/*-env.yml 15 | spruce diff spruce-diff/{prod,staging}-env.yml 16 | spruce diff spruce-diff/prod-env.yml spruce-diff/staging-env.yml 17 | ``` 18 | 19 | The output will be similar to: 20 | 21 | ``` 22 | $.applications[myapp].env[1] added 23 | DEBUG: 1 24 | 25 | $.applications[myapp].env[0].SPECIAL changed value 26 | from prod-special-token 27 | to staging-special-token 28 | 29 | $.applications[myapp].routes[0] changed value 30 | from myapp.starkandwayne.com 31 | to myapp-staging.starkandwayne.com 32 | ``` 33 | 34 | ## Matching files 35 | 36 | ``` 37 | spruce diff spruce-diff/prod-env.yml spruce-diff/prod-env.yml 38 | ``` 39 | 40 | The output will be similar to: 41 | 42 | ``` 43 | both files are semantically equivalent; no differences found! 44 | ``` 45 | -------------------------------------------------------------------------------- /spruce-diff/manifest.yml: -------------------------------------------------------------------------------- 1 | applications: 2 | - name: myapp 3 | services: 4 | - myapp-pg 5 | - myapp-redis 6 | env: 7 | - COMMON: some-common-value 8 | -------------------------------------------------------------------------------- /spruce-diff/prod-env.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: myapp 4 | routes: 5 | - myapp.starkandwayne.com 6 | env: 7 | - SPECIAL: prod-special-token 8 | -------------------------------------------------------------------------------- /spruce-diff/spruce-diff-differences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egen/tiny-tutorials/ce22d26c431c1f50b731c4521ee5c95cb6075498/spruce-diff/spruce-diff-differences.png -------------------------------------------------------------------------------- /spruce-diff/staging-env.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: myapp 4 | routes: 5 | - myapp-staging.starkandwayne.com 6 | env: 7 | - SPECIAL: staging-special-token 8 | - DEBUG: 1 9 | -------------------------------------------------------------------------------- /spruce-diff/tutorial.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | diff staging-env.yml prod-env.yml 6 | 7 | spruce diff staging-env.yml prod-env.yml 8 | 9 | spruce diff <(spruce merge manifest.yml staging-env.yml) <(spruce merge manifest.yml prod-env.yml) 10 | -------------------------------------------------------------------------------- /spruce-json/README.md: -------------------------------------------------------------------------------- 1 | # Convert YAML to JSON 2 | 3 | Since `spruce` works so well with YAML files - merging them, importing data from Vault - it can be convenient to use `spruce` even when you ultimately want JSON. Or you might want to use `jq` to find or process values within your YAML/JSON. 4 | 5 | `spruce json` makes this step easy. 6 | 7 | ![json](spruce-json.png) 8 | 9 | ``` 10 | cat >/tmp/values.yml < /tmp/docker-compose-redis-v2.yml 56 | docker-compose -f /tmp/docker-compose-redis-v2.yml up 57 | ``` 58 | 59 | Or, as in the original example, you can inline the `spruce merge` without ever creating an intermediate file: 60 | 61 | ``` 62 | docker-compose -f docker-compose.yml up 63 | docker-compose -f <(spruce merge docker-compose.yml redis-v2.yml) up 64 | docker-compose -f <(spruce merge docker-compose.yml redis-v3.yml) up 65 | ``` 66 | -------------------------------------------------------------------------------- /spruce-merge-docker-compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | redis: 4 | container_name: redis 5 | image: redis:latest 6 | ports: ["6379:6379"] 7 | -------------------------------------------------------------------------------- /spruce-merge-docker-compose/redis-v2.yml: -------------------------------------------------------------------------------- 1 | services: 2 | redis: 3 | image: redis:2.8 4 | -------------------------------------------------------------------------------- /spruce-merge-docker-compose/redis-v3.yml: -------------------------------------------------------------------------------- 1 | services: 2 | redis: 3 | image: redis:3.2.8 4 | -------------------------------------------------------------------------------- /spruce-merge-docker-compose/spruce-merge-to-build-docker-compose-yml.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egen/tiny-tutorials/ce22d26c431c1f50b731c4521ee5c95cb6075498/spruce-merge-docker-compose/spruce-merge-to-build-docker-compose-yml.gif -------------------------------------------------------------------------------- /spruce-merge-environments/README.md: -------------------------------------------------------------------------------- 1 | # Separate YAML for different environments 2 | 3 | When deploying a similar thing to different environments it is an idea to have a common YAML file, with the differences split into environment-specific YAML files. Then merge them together on demand. 4 | 5 | It can be useful to merge together small YAML files even you require JSON. 6 | 7 | ## Separate environment files 8 | 9 | In this `tiny-tutorials/spruce-merge-environments` folder there is a common `manifest.yml` YAML file, and two environment specific YAML files: 10 | 11 | ``` 12 | manifest.yml 13 | prod-env.yml 14 | staging-env.yml 15 | ``` 16 | 17 | `manifest.yml` specifies some common configuration: 18 | 19 | ```yaml 20 | --- 21 | applications: 22 | - name: myapp 23 | services: 24 | - myapp-pg 25 | - myapp-redis 26 | env: 27 | - COMMON: some-common-value 28 | ``` 29 | 30 | The `prod-env.yml` adds or overrides anything specifically for the `prod` environment: 31 | 32 | ```yaml 33 | --- 34 | applications: 35 | - name: myapp 36 | env: 37 | - SPECIAL: prod-special-token 38 | ``` 39 | 40 | It is simple to merge them for `prod` environment: 41 | 42 | ``` 43 | cd spruce-merge-environments 44 | spruce merge manifest.yml prod-env.yml 45 | ``` 46 | 47 | The resulting environment manifest: 48 | 49 | ```yaml 50 | applications: 51 | - env: 52 | - COMMON: some-common-value 53 | SPECIAL: prod-special-token 54 | name: myapp 55 | services: 56 | - myapp-pg 57 | - myapp-redis 58 | ``` 59 | 60 | For the `staging` environment: 61 | 62 | ``` 63 | spruce merge manifest.yml staging-env.yml 64 | ``` 65 | 66 | The resulting environment manifest: 67 | 68 | ```yaml 69 | applications: 70 | - env: 71 | - COMMON: some-common-value 72 | SPECIAL: staging-special-token 73 | - DEBUG: 1 74 | name: myapp 75 | services: 76 | - myapp-pg 77 | - myapp-redis 78 | ``` 79 | 80 | ## Merging by name 81 | 82 | How did `spruce merge` combine the two files' `applications` array items? 83 | 84 | `spruce merge` cleverly uses of the `name` key of a YAML object to identify whether two YAML objects are merged together, or if they are added together into the resulting array. 85 | 86 | ## Merge into new file 87 | 88 | If the on-demand `spruce merge` output is to be passed as a file into a CLI command, you can either: 89 | 90 | * store the `spruce merge` output into new file; or 91 | * pass the output via a temporary file descriptor (next section) 92 | 93 | ``` 94 | spruce merge manifest.yml staging-env.yml > manifest.staging.yml 95 | cf push -f manifest.staging.yml 96 | ``` 97 | 98 | ## Merge without writing new file 99 | 100 | Using file descriptors, it is possible to skip/avoid the step of ever writing the merged results to a file: 101 | 102 | ``` 103 | cf push -f <(spruce merge manifest.yml staging-env.yml) 104 | ``` 105 | 106 | The net result is the same as the preceding section. The `cf push -f ` command behaves as if it received a file. 107 | -------------------------------------------------------------------------------- /spruce-merge-environments/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: myapp 4 | services: 5 | - myapp-pg 6 | - myapp-redis 7 | env: 8 | - COMMON: some-common-value 9 | -------------------------------------------------------------------------------- /spruce-merge-environments/prod-env.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: myapp 4 | env: 5 | - SPECIAL: prod-special-token 6 | -------------------------------------------------------------------------------- /spruce-merge-environments/staging-env.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: myapp 4 | env: 5 | - SPECIAL: staging-special-token 6 | - DEBUG: 1 7 | -------------------------------------------------------------------------------- /spruce-merge-environments/tutorial.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | spruce merge manifest.yml staging-env.yml 6 | spruce merge manifest.yml prod-env.yml 7 | 8 | diff <(spruce merge manifest.yml staging-env.yml) <(spruce merge manifest.yml prod-env.yml) 9 | 10 | spruce diff <(spruce merge manifest.yml staging-env.yml) <(spruce merge manifest.yml prod-env.yml) 11 | --------------------------------------------------------------------------------