├── .gitignore
├── tests
├── project
│ ├── Gunfile.prod
│ ├── Gunfile.stage
│ ├── cmds
│ │ ├── foo.bash
│ │ ├── auto.bash
│ │ └── bar.bash
│ └── Gunfile
└── project.bash
├── .github
├── dependabot.yml
└── workflows
│ └── ci.yml
├── lib
├── README.md
├── aws
│ ├── aws.bash
│ ├── util.bash
│ └── ec2.bash
├── releases
│ ├── config.bash
│ └── releases.bash
└── consul
│ └── consul.bash
├── go.mod
├── example
├── cmds
│ └── github.bash
└── README.md
├── README.md
├── src
├── env.bash
├── color.bash
├── fn.bash
├── deps.bash
├── module.bash
├── cmd.bash
└── gun.bash
├── go.sum
├── .circleci
└── config.yml
├── LICENSE
├── Makefile
├── glidergun.go
└── CHANGELOG.md
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | release
3 | bindata.go
4 | tests/project/.gun
5 |
--------------------------------------------------------------------------------
/tests/project/Gunfile.prod:
--------------------------------------------------------------------------------
1 | export AWS_ACCESS_KEY_ID=prod-access-key
2 | export AWS_SECRET_ACCESS_KEY=prod-secret
--------------------------------------------------------------------------------
/tests/project/Gunfile.stage:
--------------------------------------------------------------------------------
1 | export AWS_ACCESS_KEY_ID=staging-access-key
2 | export AWS_SECRET_ACCESS_KEY=staging-secret
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 2
3 | updates:
4 | - package-ecosystem: "gomod"
5 | directory: "/"
6 | schedule:
7 | interval: "daily"
8 |
--------------------------------------------------------------------------------
/tests/project/cmds/foo.bash:
--------------------------------------------------------------------------------
1 |
2 |
3 | init() {
4 | cmd-export-ns foo "Foo namespace"
5 | cmd-export foo-hello
6 | env-import AWS_ACCESS_KEY_ID ""
7 | }
8 |
9 | foo-hello() {
10 | echo "Hello from foo"
11 | }
12 |
--------------------------------------------------------------------------------
/tests/project/Gunfile:
--------------------------------------------------------------------------------
1 |
2 | init() {
3 | cmd-export hello
4 | cmd-export aliased-cmd aliased
5 | }
6 |
7 | hello() {
8 | declare desc="Says hello"
9 | echo "Hello!"
10 | }
11 |
12 | aliased-cmd() {
13 | echo "aliased"
14 | }
15 |
--------------------------------------------------------------------------------
/tests/project/cmds/auto.bash:
--------------------------------------------------------------------------------
1 | cmd:exported() {
2 | declare desc="Automatically exported command"
3 | echo "exported"
4 | }
5 |
6 | cmd:lazy-command() {
7 | declare desc="Look ma no init"
8 | echo "exported too"
9 | }
10 |
--------------------------------------------------------------------------------
/lib/README.md:
--------------------------------------------------------------------------------
1 | # glidergun module library
2 |
3 | ## Require example
4 | ```
5 | init() {
6 | module-require github.com/gliderlabs/glidergun/lib/aws
7 | }
8 | ```
9 |
10 | ## Updating a module
11 |
12 | $ gun :get github.com/gliderlabs/glidergun/lib/aws
13 |
--------------------------------------------------------------------------------
/tests/project/cmds/bar.bash:
--------------------------------------------------------------------------------
1 |
2 | init() {
3 | cmd-export-ns bar "Bar namespace"
4 | cmd-export bar-hello
5 | #deps-require jq 1.4
6 | #module-require github.com/gliderlabs/glidergun/lib/aws
7 | }
8 |
9 | bar-hello() {
10 | echo "Hello from bar"
11 | }
12 |
--------------------------------------------------------------------------------
/lib/aws/aws.bash:
--------------------------------------------------------------------------------
1 | # Shared AWS functions wrapping aws-cli
2 |
3 | init() {
4 | env-import AWS_DEFAULT_REGION
5 | env-import AWS_ACCESS_KEY_ID
6 | env-import AWS_SECRET_ACCESS_KEY
7 |
8 | deps-require aws
9 | deps-require jq 1.4
10 | }
11 |
12 | aws-json() {
13 | aws --output json $@
14 | }
15 |
16 | aws-text() {
17 | aws --output text $@
18 | }
19 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module glidergun
2 |
3 | go 1.17
4 |
5 | require (
6 | github.com/progrium/go-basher v5.1.6+incompatible
7 | gopkg.in/inconshreveable/go-update.v0 v0.0.0-20150814200126-d8b0b1d421aa
8 | )
9 |
10 | require (
11 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
12 | github.com/kr/binarydist v0.1.0 // indirect
13 | github.com/mitchellh/go-homedir v1.1.0 // indirect
14 | )
15 |
--------------------------------------------------------------------------------
/lib/aws/util.bash:
--------------------------------------------------------------------------------
1 | # Temporary place for utility functions
2 |
3 | parallel() {
4 | declare cmd="$@"
5 | declare -a pids
6 | for line in $(cat); do
7 | eval "${cmd//\{\}/$line} &"
8 | pids+=($!)
9 | done
10 | local failed=$((0))
11 | for pid in ${pids[@]}; do
12 | if ! wait $pid; then
13 | failed=$((failed + 1))
14 | fi
15 | done
16 | return $((failed))
17 | }
18 |
19 | ssh-cmd() {
20 | echo "ssh -A $SSH_OPTS $@"
21 | }
22 |
--------------------------------------------------------------------------------
/example/cmds/github.bash:
--------------------------------------------------------------------------------
1 | init() {
2 | deps-require jq
3 |
4 | cmd-export gh-orgs
5 | env-import GITHUB_TOKEN
6 | env-import PAGE_SIZE 5
7 | }
8 |
9 | gh-orgs() {
10 | declare desc="Lists your GithHub organizations"
11 |
12 | : ${GITHUB_TOKEN:? go to https://github.com/settings/tokens to create one}
13 | curl -s \
14 | -H "Authorization: Bearer $GITHUB_TOKEN" \
15 | https://api.github.com/user/orgs?per_page=$PAGE_SIZE \
16 | | jq ".[].login" -r
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # glidergun
2 |
3 | [](https://circleci.com/gh/gliderlabs/glidergun)
4 | [](https://kiwiirc.com/client/irc.freenode.net/#gliderlabs)
5 |
6 |
7 | ## Install
8 | $ curl -L https://github.com/gliderlabs/glidergun/releases/download/v0.1.0/glidergun_0.1.0_$(uname -sm|tr \ _).tgz \
9 | | tar -zxC /usr/local/bin
10 |
11 | ## Tutorial
12 |
13 | See [example/README.md](example/README.md) for a detailed tutorial.
14 |
15 |
--------------------------------------------------------------------------------
/src/env.bash:
--------------------------------------------------------------------------------
1 |
2 | declare -a _env
3 |
4 | env-import() {
5 | declare var="$1" default="$2"
6 | if [[ -z "${!var+x}" ]]; then
7 | if [[ -z "${2+x}" ]]; then
8 | echo "!! Imported variable $var must be set in profile or environment." | >&2 red
9 | exit 2
10 | else
11 | export $var="$default"
12 | fi
13 | fi
14 | _env+=($var)
15 | }
16 |
17 | env-show() {
18 | declare desc="Shows relevant environment variables"
19 | local longest=0
20 | for var in "${_env[@]}"; do
21 | if [[ "${#var}" -gt "$longest" ]]; then
22 | longest="${#var}"
23 | fi
24 | done
25 | for var in "${_env[@]}"; do
26 | printf "%-${longest}s = %s\n" "$var" "${!var}"
27 | done
28 | }
29 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: ci
3 |
4 | on:
5 | pull_request:
6 | branches:
7 | - '*'
8 | push:
9 | branches:
10 | - 'master'
11 | - 'release'
12 |
13 | jobs:
14 | ci:
15 | name: ci
16 | runs-on: ubuntu-18.04
17 | strategy:
18 | fail-fast: true
19 | steps:
20 | - uses: actions/checkout@v2
21 | - uses: actions/setup-go@v2
22 | with:
23 | go-version: '1.17.3'
24 | - run: make deps build
25 | - run: make test
26 | - name: upload packages
27 | uses: actions/upload-artifact@v2
28 | with:
29 | name: build
30 | path: build/**/*
31 | - name: make release-in-docker
32 | run: |
33 | if [[ "${GITHUB_REF#refs/heads/}" == "release" ]]; then
34 | make deps build release
35 | fi
36 |
--------------------------------------------------------------------------------
/src/color.bash:
--------------------------------------------------------------------------------
1 |
2 | declare -A color_table=(
3 | ["red"]='\033[00;31m'
4 | ["green"]='\033[00;32m'
5 | ["yellow"]='\033[00;33m'
6 | ["blue"]='\033[00;34m'
7 | ["purple"]='\033[00;35m'
8 | ["cyan"]="\033[00;36m"
9 | ["white"]='\033[00;37m'
10 | ["red-bright"]='\033[01;31m'
11 | ["green-bright"]='\033[01;32m'
12 | ["yellow-bright"]='\033[01;33m'
13 | ["blue-bright"]='\033[01;34m'
14 | ["purple-bright"]='\033[01;35m'
15 | ["cyan-bright"]='\033[01;36m'
16 | ["white-bright"]='\033[01;37m'
17 | )
18 |
19 | color-init() {
20 | for color in "${!color_table[@]}"; do
21 | eval "$color() { color-cat $color; }"
22 | done
23 | }
24 |
25 | color-cat() {
26 | declare color="$1"
27 | while read -r; do
28 | printf "${color_table[$color]}%s\033[0m\n" "$REPLY"
29 | done
30 | }
31 |
32 | color-cycle() {
33 | colors=(${!color_table[@]})
34 | index="$((($BASHPID%${#color_table[@]})))"
35 | color-cat "${colors[$index]}"
36 | }
37 |
--------------------------------------------------------------------------------
/src/fn.bash:
--------------------------------------------------------------------------------
1 | # Bash function introspection
2 |
3 | fn-args() {
4 | declare desc="Inspect a function's arguments"
5 | local argline=$(type $1 | grep declare | grep -v "declare desc" | head -1)
6 | echo -e "${argline// /"\n"}" | awk -F= '/=/{print "<"$1">"}' | tr "\n" " "
7 | }
8 |
9 | fn-desc() {
10 | declare desc="Inspect a function's description"
11 | desc=""
12 | eval "$(type $1 | grep desc | head -1)"; echo $desc
13 | }
14 |
15 | fn-info() {
16 | declare desc="Inspects a function"
17 | declare fn="$1" showsource="$2"
18 | echo "$fn $(fn-args $fn)"
19 | echo " $(fn-desc $fn)"
20 | echo
21 | if [[ "$showsource" ]]; then
22 | declare -f "$fn" | tail -n +1
23 | echo
24 | fi
25 | }
26 |
27 | fn-source() {
28 | declare desc="Shows function source"
29 | declare fn="$1"
30 | declare -f "$fn" | tail -n +2
31 | }
32 |
33 | fn-call() {
34 | declare desc="Run function in environment"
35 | declare fn="$1"; shift
36 | "$fn" "$@"
37 | }
38 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
2 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
3 | github.com/kr/binarydist v0.1.0 h1:6kAoLA9FMMnNGSehX0s1PdjbEaACznAv/W219j2uvyo=
4 | github.com/kr/binarydist v0.1.0/go.mod h1:DY7S//GCoz1BCd0B0EVrinCKAZN3pXe+MDaIZbXQVgM=
5 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
6 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
7 | github.com/progrium/go-basher v5.1.6+incompatible h1:4C9YrvISyUN01W2gEruN1EzMecc75NDiHUjipKuOqls=
8 | github.com/progrium/go-basher v5.1.6+incompatible/go.mod h1:Oiy7jZEU1mm+gI1dt5MKYwxptmD37q8/UupxnwhMHtI=
9 | gopkg.in/inconshreveable/go-update.v0 v0.0.0-20150814200126-d8b0b1d421aa h1:drvf2JoUL1fz3ttkGNkw+rf3kZa2//7XkYGpSO4NHNA=
10 | gopkg.in/inconshreveable/go-update.v0 v0.0.0-20150814200126-d8b0b1d421aa/go.mod h1:tuNm0ntQ7IH9VSA39XxzLMpee5c2DwgIbjD4x3ydo8Y=
11 |
--------------------------------------------------------------------------------
/lib/releases/config.bash:
--------------------------------------------------------------------------------
1 | # Release config commands
2 | # WARNING: work in progress
3 |
4 | init() {
5 | cmd-export-ns config "Manage service config"
6 | cmd-export config-get
7 | cmd-export config-set
8 | cmd-export config-unset
9 | cmd-export config-edit
10 | }
11 |
12 | config-edit() {
13 | declare desc="Edit service config in Consul UI (OS X)"
14 | declare service="$1"
15 | consul-open "/dc1/kv/${EC2_VPC:?}/config/${service:?}/"
16 | }
17 |
18 | config-get() {
19 | declare desc="Get service config"
20 | declare service="$1" key="$2"
21 | if [[ "$key" ]]; then
22 | consul-get "${EC2_VPC:?}/config/${service:?}/${key:?}"
23 | else
24 | consul-export "${EC2_VPC:?}/config/${service:?}"
25 | fi
26 | }
27 |
28 | config-set() {
29 | declare desc="Set service config"
30 | declare service="$1" key="$2" value="$3"
31 | consul-set "${EC2_VPC:?}/config/${service:?}/${key:?}" "$value"
32 | }
33 |
34 | config-unset() {
35 | declare desc="Unset service config"
36 | declare service="$1" key="$2"
37 | consul-del "${EC2_VPC:?}/config/${service:?}/${key:?}"
38 | }
39 |
--------------------------------------------------------------------------------
/tests/project.bash:
--------------------------------------------------------------------------------
1 |
2 | export GUN="${GUN:-gun}"
3 |
4 | setup() {
5 | cd "$(dirname "${BASH_SOURCE[0]}")/project"
6 | $GUN &> /dev/null
7 | }
8 | setup
9 |
10 | T_hello-cmd() {
11 | result="$($GUN hello)"
12 | [[ "$result" == "Hello!" ]]
13 | }
14 |
15 | T_foo-namespace-cmd() {
16 | result="$($GUN foo hello)"
17 | [[ "$result" == "Hello from foo" ]]
18 | }
19 |
20 | T_bar-namespace-cmd() {
21 | result="$($GUN bar hello)"
22 | [[ "$result" == "Hello from bar" ]]
23 | }
24 |
25 | T_foo-env-import() {
26 | [[ "$($GUN :env | grep AWS_ACCESS_KEY_ID)" ]]
27 | }
28 |
29 | T_prod-profile() {
30 | result="$($GUN prod :env)"
31 | [[ "$result" == "AWS_ACCESS_KEY_ID = prod-access-key" ]]
32 | }
33 |
34 | T_stage-profile() {
35 | result="$($GUN stage :env)"
36 | [[ "$result" == "AWS_ACCESS_KEY_ID = staging-access-key" ]]
37 | }
38 |
39 | T_aliased-cmd() {
40 | result="$($GUN aliased)"
41 | [[ "$result" == "aliased" ]]
42 | }
43 |
44 | T_autoexported-cmd() {
45 | result="$($GUN exported)"
46 | [[ "$result" == "exported" ]]
47 | }
48 |
49 | T_autoexported-cmd-with-dash() {
50 | result="$($GUN lazy-command)"
51 | [[ "$result" == "exported too" ]]
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 2.1
3 |
4 | jobs:
5 | test:
6 | docker:
7 | - image: circleci/golang:1.12
8 | steps:
9 | - checkout
10 | - restore_cache:
11 | keys:
12 | - go-mod-v4-{{ checksum "go.sum" }}
13 | - run:
14 | name: Prepare environment
15 | command: make deps build
16 | - run:
17 | name: Run unit tests
18 | command: make test
19 | - run:
20 | name: Prepare artifacts
21 | command: |
22 | mkdir -p build/workspace/
23 | tar -czvf build/workspace/go-workspace.tgz -C ~/.go_workspace .
24 | - save_cache:
25 | key: go-mod-v4-{{ checksum "go.sum" }}
26 | paths:
27 | - "/go/pkg/mod"
28 | - store_artifacts:
29 | path: build
30 | destination: build
31 |
32 | release:
33 | docker:
34 | - image: circleci/golang:1.12
35 | steps:
36 | - checkout
37 | - restore_cache:
38 | keys:
39 | - go-mod-v4-{{ checksum "go.sum" }}
40 | - run:
41 | name: Release
42 | command: |
43 | echo "make deps build release"
44 |
45 | workflows:
46 | build:
47 | jobs:
48 | - test
49 | - release:
50 | filters:
51 | branches:
52 | only:
53 | - release
54 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Glider Labs
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of glidergun nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | NAME = glidergun
2 | BINARYNAME=gun
3 | OWNER =gliderlabs
4 | HARDWARE = $(shell uname -m)
5 | SYSTEM_NAME = $(shell uname -s | tr '[:upper:]' '[:lower:]')
6 | VERSION=0.1.0
7 |
8 | build: src
9 | mkdir -p build/Linux && GOOS=linux CGO_ENABLED=0 go build -a \
10 | -installsuffix cgo \
11 | -ldflags "-X main.Version=$(VERSION)" \
12 | -o build/Linux/$(BINARYNAME)
13 | mkdir -p build/Darwin && GOOS=darwin CGO_ENABLED=0 go build -a \
14 | -installsuffix cgo \
15 | -ldflags "-X main.Version=$(VERSION)" \
16 | -o build/Darwin/$(BINARYNAME)
17 |
18 | test: src
19 | go install
20 | GUN=glidergun basht tests/*.bash
21 |
22 | src:
23 | go-bindata src
24 |
25 | install: build
26 | install build/$(shell uname -s)/gun /usr/local/bin
27 |
28 | deps: gh-release
29 | cd / && go get -u github.com/jteeuwen/go-bindata/...
30 | cd / && go get -u github.com/progrium/basht/...
31 |
32 | gh-release:
33 | mkdir -p bin
34 | curl -o bin/gh-release.tgz -sL https://github.com/progrium/gh-release/releases/download/v2.3.3/gh-release_2.3.3_$(SYSTEM_NAME)_$(HARDWARE).tgz
35 | tar xf bin/gh-release.tgz -C /usr/local/bin
36 | chmod +x /usr/local/bin/gh-release
37 |
38 | release:
39 | rm -rf release && mkdir release
40 | tar -zcf release/$(NAME)_$(VERSION)_Linux_$(HARDWARE).tgz -C build/Linux $(BINARYNAME)
41 | tar -zcf release/$(NAME)_$(VERSION)_Darwin_$(HARDWARE).tgz -C build/Darwin $(BINARYNAME)
42 | gh-release checksums sha256
43 | gh-release create $(OWNER)/$(NAME) $(VERSION) $(shell git rev-parse --abbrev-ref HEAD) v$(VERSION)
44 |
45 | clean:
46 | rm -rf build release
47 |
48 | .PHONY: build release src
49 |
--------------------------------------------------------------------------------
/lib/releases/releases.bash:
--------------------------------------------------------------------------------
1 | # Release management commands
2 | # WARNING: work in progress
3 |
4 | init() {
5 | cmd-export-ns release "Release management"
6 | cmd-export release-create
7 | cmd-export release-show
8 | cmd-export release-latest
9 | cmd-export release-current
10 | cmd-export release-get
11 | }
12 |
13 | release-create() {
14 | declare desc="Create a new release"
15 | declare service="$1"
16 | if ! release-latest "$service" > /dev/null 2>&1; then
17 | #consul-set "${EC2_VPC:?}/releases/${service:?}/current" "v0"
18 | consul-set "${EC2_VPC:?}/releases/${service:?}/latest" "v0"
19 | fi
20 | local snapshot latest
21 | snapshot="$(consul-export "${EC2_VPC:?}/config/${service:?}" || true)"
22 | latest="$(release-latest $service)"
23 | latest="v$((${latest/v/}+1))"
24 | consul-set "${EC2_VPC:?}/releases/${service:?}/$latest" "$snapshot"
25 | consul-set "${EC2_VPC:?}/releases/${service:?}/latest" "$latest"
26 | echo "$latest"
27 | }
28 |
29 | release-get() {
30 | declare desc="Get contents of existing release"
31 | declare service="$1" version="$2"
32 | consul-get "${EC2_VPC:?}/releases/${service:?}/v${version/v/}"
33 | }
34 |
35 | release-current() {
36 | declare desc="Get or set current release version"
37 | declare service="$1" version="$2"
38 | if [[ "$version" ]]; then
39 | release-get "$service" "$version" > /dev/null
40 | consul-set "${EC2_VPC:?}/releases/${service:?}/current" "v${version/v/}"
41 | fi
42 | consul-get "${EC2_VPC:?}/releases/${service:?}/current"
43 | }
44 |
45 | release-latest() {
46 | declare desc="Get latest release version"
47 | declare service="$1"
48 | consul-get "${EC2_VPC:?}/releases/${service:?}/latest"
49 | }
50 |
51 | release-show() {
52 | declare desc="Show releases information"
53 | declare service="$1"
54 | echo "Latest: $(release-latest $service)"
55 | echo "Current: $(release-current $service)"
56 | consul-ls "${EC2_VPC:?}/releases/${service:?}" \
57 | | grep -v latest \
58 | | grep -v current
59 | }
60 |
--------------------------------------------------------------------------------
/src/deps.bash:
--------------------------------------------------------------------------------
1 | # Simple binary dependency management
2 |
3 | declare DEPS_REPO="${DEPS_REPO:-https://raw.githubusercontent.com/gliderlabs/glidergun-rack/master/index}"
4 |
5 | deps-init() {
6 | export PATH="$GUN_DIR/bin:$PATH"
7 | }
8 |
9 | deps-require() {
10 | declare name="$1" version="${2:-latest}"
11 | deps-check "$name" "$version" && return
12 | echo "* Dependency required, installing $name $version ..." | >&2 yellow
13 | deps-install "$name" "$version"
14 | }
15 |
16 | deps-check() {
17 | declare name="$1" version="${2:-latest}"
18 | [[ -e "$GUN_DIR/bin/$name" ]]
19 | }
20 |
21 | deps-install() {
22 | declare name="$1" version="${2:-latest}"
23 | local tag index gundir bindir tmpdir tmpfile dep filename extension install
24 | gundir="$(cd $GUN_DIR; pwd)"
25 | bindir="$gundir/bin"
26 | mkdir -p "$bindir"
27 | index=$(curl -s "$DEPS_REPO/$name")
28 | tag="$(uname -s)_$(uname -m | grep -s 64 > /dev/null && echo amd64 || echo 386)"
29 | if ! dep="$(echo "$index" | grep -i -e "^$version $tag " -e "^$version \* ")"; then
30 | echo "!! Dependency not in index: $name $version" | >&2 red
31 | exit 2
32 | fi
33 | IFS=' ' read v t url checksum <<< "$dep"
34 | tmpdir="$gundir/tmp"
35 | mkdir -p "$tmpdir"
36 | tmpfile="${tmpdir:?}/$name"
37 | curl -Ls $url > "$tmpfile"
38 | if [[ "$checksum" ]]; then
39 | if ! [[ "$(cat "$tmpfile" | checksum md5)" = "$checksum" ]]; then
40 | echo "!! Dependency checksum failed: $name $version $checksum" | >&2 red
41 | exit 2
42 | fi
43 | fi
44 | cd "$tmpdir"
45 | filename="$(basename "$url")"
46 | extension="${filename#*.}"
47 | case "$extension" in
48 | zip) unzip "$tmpfile" > /dev/null;;
49 | tgz|tar.gz) tar -zxf "$tmpfile" > /dev/null;;
50 | esac
51 | install="$(echo "$index" | grep "^# install: " || true)"
52 | if [[ "$install" ]]; then
53 | IFS=':' read _ script <<< "$install"
54 | export PREFIX="$gundir"
55 | eval "$script" > /dev/null
56 | unset PREFIX
57 | else
58 | chmod +x "$tmpfile"
59 | mv "$tmpfile" "$bindir"
60 | fi
61 | cd - > /dev/null
62 | rm -rf "${tmpdir:?}"
63 | deps-check "$name" "$version"
64 | }
65 |
--------------------------------------------------------------------------------
/src/module.bash:
--------------------------------------------------------------------------------
1 |
2 | declare GUN_REMOTE_MODULES="${GUN_REMOTE_MODULES:-$GUN_DIR/modules}"
3 |
4 | module-load() {
5 | declare filename="$1"
6 | source "$filename"
7 | if grep '^init()' "$filename" > /dev/null; then
8 | init
9 | fi
10 |
11 | module-auto-export $filename
12 | }
13 |
14 | module-auto-export() {
15 | declare filename="$1"
16 |
17 | local autoprefix="cmd:"
18 | while read cmd; do
19 | cmd-export "cmd:$cmd" "$cmd"
20 | done < <(
21 | sed -n "s/^cmd:\([^(]*\)(.*/\1/p" "$filename"
22 | )
23 |
24 | }
25 |
26 | module-load-dir() {
27 | declare dir="$1"
28 | shopt -s nullglob
29 | for path in $dir/*.bash; do
30 | module-load "$path"
31 | done
32 | shopt -u nullglob
33 | }
34 |
35 | module-load-remote() {
36 | shopt -s nullglob
37 | for module in $GUN_REMOTE_MODULES/**/*.bash; do
38 | module-load "$module"
39 | done
40 | shopt -u nullglob
41 | }
42 |
43 | module-require() {
44 | declare url="$1" as="$2"
45 | module-check "$url" "$as" && return
46 | if [[ ! "$as" ]]; then
47 | as="$(basename ${url%%.git})"
48 | fi
49 | echo "* Module required, installing $as from $url ..." | >&2 yellow
50 | module-get "$url" "$as"
51 | module-load-dir "$GUN_REMOTE_MODULES/$as"
52 | }
53 |
54 | module-check() {
55 | declare url="$1" as="$2"
56 | : "${url:?}"
57 | if [[ ! "$as" ]]; then
58 | as="$(basename ${url%%.git})"
59 | fi
60 | [[ -d "$GUN_REMOTE_MODULES/$as" ]]
61 | }
62 |
63 | module-get() {
64 | declare desc="Install or update a remote module by URL"
65 | declare url="$1" as="$2"
66 | : "${url:?}"
67 | if [[ ! "$(which git)" ]]; then
68 | echo "!! git is required for fetching modules"| >&2 red
69 | exit 2
70 | fi
71 | if [[ ! "$url" =~ ^http ]]; then
72 | url="http://$url"
73 | fi
74 | if [[ ! "$as" ]]; then
75 | as="$(basename ${url%%.git})"
76 | fi
77 | local tmproot tmprepo scheme domain user repo path
78 | IFS="/" read scheme _ domain user repo path <<< "$url"
79 | tmproot="${GUN_DIR:?}/tmp"
80 | tmprepo="$tmproot/$domain/$user/$repo"
81 | mkdir -p "$tmprepo"
82 | git clone --quiet --depth 1 "$scheme//$domain/$user/$repo" "$tmprepo"
83 | rm -rf "${tmprepo:?}/.git"
84 | rm -rf "${GUN_REMOTE_MODULES:?}/$as"
85 | mkdir -p "$GUN_REMOTE_MODULES"
86 | mv "$tmprepo/$path" "$GUN_REMOTE_MODULES/$as"
87 | rm -rf "${tmproot:?}"
88 | }
89 |
--------------------------------------------------------------------------------
/glidergun.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "archive/tar"
5 | "bytes"
6 | "compress/gzip"
7 | "crypto/md5"
8 | "crypto/sha1"
9 | "crypto/sha256"
10 | "encoding/hex"
11 | "fmt"
12 | "hash"
13 | "io"
14 | "io/ioutil"
15 | "net/http"
16 | "os"
17 |
18 | "github.com/progrium/go-basher"
19 | "gopkg.in/inconshreveable/go-update.v0"
20 | )
21 |
22 | const (
23 | LatestDownloadUrl = "https://dl.gliderlabs.com/glidergun/latest/%s.tgz"
24 | )
25 |
26 | var Version string
27 |
28 | func fatal(msg string) {
29 | println("!!", msg)
30 | os.Exit(2)
31 | }
32 |
33 | func Selfupdate(args []string) {
34 | up := update.New()
35 | err := up.CanUpdate()
36 | if err != nil {
37 | fatal("Can't update because: '" + err.Error() + "'. Try as root?")
38 | }
39 | checksumExpected, err := hex.DecodeString(args[1])
40 | if err != nil {
41 | fatal(err.Error())
42 | }
43 | url := fmt.Sprintf(LatestDownloadUrl, args[0])
44 | fmt.Printf("Downloading %v ...\n", url)
45 | resp, err := http.Get(url)
46 | if err != nil {
47 | fatal(err.Error())
48 | }
49 | defer resp.Body.Close()
50 | buf := new(bytes.Buffer)
51 | data, err := ioutil.ReadAll(io.TeeReader(resp.Body, buf))
52 | if err != nil {
53 | fatal(err.Error())
54 | }
55 | checksum := sha256.New().Sum(data)
56 | if bytes.Equal(checksum, checksumExpected) {
57 | fatal("Checksum failed. Got: " + fmt.Sprintf("%x", checksum))
58 | }
59 | z, err := gzip.NewReader(buf)
60 | if err != nil {
61 | fatal(err.Error())
62 | }
63 | defer z.Close()
64 | t := tar.NewReader(z)
65 | hdr, err := t.Next()
66 | if err != nil {
67 | fatal(err.Error())
68 | }
69 | if hdr.Name != "gun" {
70 | fatal("glidergun binary not found in downloaded tarball")
71 | }
72 | err, errRecover := up.FromStream(t)
73 | if err != nil {
74 | fmt.Printf("Update failed: %v\n", err)
75 | if errRecover != nil {
76 | fmt.Printf("Failed to recover bad update: %v!\n", errRecover)
77 | fmt.Printf("Program exectuable may be missing!\n")
78 | }
79 | os.Exit(2)
80 | }
81 | fmt.Println("Updated.")
82 | }
83 |
84 | func Checksum(args []string) {
85 | if len(args) < 1 {
86 | fatal("No algorithm specified")
87 | }
88 | var h hash.Hash
89 | switch args[0] {
90 | case "md5":
91 | h = md5.New()
92 | case "sha1":
93 | h = sha1.New()
94 | case "sha256":
95 | h = sha256.New()
96 | default:
97 | fatal("Algorithm '" + args[0] + "' is unsupported")
98 | }
99 | io.Copy(h, os.Stdin)
100 | fmt.Printf("%x\n", h.Sum(nil))
101 | }
102 |
103 | func main() {
104 | os.Setenv("GUN_VERSION", Version)
105 | basher.Application(map[string]func([]string){
106 | "checksum": Checksum,
107 | "selfupdate": Selfupdate,
108 | }, []string{
109 | "src/fn.bash",
110 | "src/cmd.bash",
111 | "src/env.bash",
112 | "src/gun.bash",
113 | "src/module.bash",
114 | "src/deps.bash",
115 | "src/color.bash",
116 | }, Asset, true)
117 | }
118 |
--------------------------------------------------------------------------------
/src/cmd.bash:
--------------------------------------------------------------------------------
1 | # A lightweight subcommand framework
2 |
3 | declare -A CMDS
4 |
5 | cmd-list() {
6 | declare desc="Lists available commands"
7 | declare ns="$1"
8 | cmd-list-keys "$ns" | sed "s/$ns://"
9 | }
10 |
11 | cmd-list-keys() {
12 | declare ns="$1"
13 | for k in "${!CMDS[@]}"; do
14 | echo "$k"
15 | done | grep "^$ns:" | sort
16 | }
17 |
18 | cmd-list-ns() {
19 | for k in "${!CMDS[@]}"; do
20 | echo "$k"
21 | done | grep -v : | sort
22 | }
23 |
24 | cmd-export() {
25 | declare desc="Exports a function as a command"
26 | declare fn="$1" as="${2:-$1}"
27 | local ns=""
28 | for n in $(cmd-list-ns); do
29 | echo "$fn" | grep "^$n-" &> /dev/null && ns="$n"
30 | done
31 | CMDS["$ns:${as/#$ns-/}"]="$fn"
32 | }
33 |
34 | cmd-export-ns() {
35 | declare ns="$1" desc="$2"
36 | eval "$1() {
37 | declare desc=\"$desc\"
38 | cmd-ns $1 \"\$@\";
39 | }"
40 | cmd-export "$1"
41 | CMDS["$1"]="$1"
42 | }
43 |
44 | cmd-ns() {
45 | local ns="$1"; shift
46 | local cmd="$1"; shift || true
47 | local status=0
48 | if [[ "${CMDS["$ns:$cmd"]+exists}" ]]; then
49 | ${CMDS["$ns:$cmd"]} "$@"
50 | else
51 | if [[ "$cmd" ]]; then
52 | echo "No such command: $cmd"
53 | status=2
54 | elif [[ "$ns" ]]; then
55 | echo "$(fn-desc "$ns")"
56 | fi
57 | cmd-available "$ns"
58 | exit $status
59 | fi
60 | }
61 |
62 | cmd-available() {
63 | declare ns="$1" full="$2"
64 | echo
65 | echo "Available commands:"
66 | for cmd in $(cmd-list "$ns" | grep -v '^:'); do
67 | printf " %-24s %s\n" "$cmd" "$(fn-desc "${CMDS["$ns:$cmd"]}")"
68 | if [[ "$full" ]]; then
69 | for subcmd in $(cmd-list "$cmd"); do
70 | printf " %-24s %s\n" "$subcmd" "$(fn-desc "${CMDS["$cmd:$subcmd"]}")"
71 | done
72 | fi
73 | done
74 | echo
75 | for specialcmd in $(cmd-list "$ns" | grep '^:'); do
76 | printf " %-24s %s\n" "$specialcmd" "$(fn-desc "${CMDS["$ns:$specialcmd"]}")"
77 | done
78 | echo
79 | }
80 |
81 | cmd-help() {
82 | declare desc="Shows help information for a command"
83 | declare args="$@"
84 | if [[ "$args" ]]; then
85 | for cmd; do true; done # last arg
86 | local ns="${args/%$cmd/}"; ns="${ns/% /}"; ns="${ns/ /-}"
87 | local fn="${CMDS["$ns:$cmd"]}"
88 | fn-info "$fn" 1
89 | else
90 | cmd-available "" 1
91 | fi
92 | }
93 |
94 | cmd-bash-complete-ns() {
95 | for n in $(cmd-list-ns); do
96 | echo " $n) COMPREPLY=(\$(compgen -W '$(cmd-list $n | xargs)' -- \$act)) ;;"
97 | done
98 | }
99 |
100 | cmd-bash-complete() {
101 | declare desc='Generates bash autocomplete function: eval "$(gun :complete)"'
102 |
103 | cat << EOF
104 | _${SELF}_comp() {
105 | local act=\${COMP_WORDS[\$COMP_CWORD]}
106 | case \$COMP_CWORD in
107 | 1) COMPREPLY=(\$(compgen -W "$(cmd-list | xargs)" -- \$act )) ;;
108 | 2) case \${COMP_WORDS[1]} in
109 | $(cmd-bash-complete-ns)
110 | esac
111 | esac
112 | }; complete -F _${SELF}_comp $SELF
113 | EOF
114 | }
115 |
--------------------------------------------------------------------------------
/lib/consul/consul.bash:
--------------------------------------------------------------------------------
1 | # Commands for working with Consul KV
2 |
3 | init() {
4 | env-import CONSUL_URL
5 | env-import CONSUL_AUTH ""
6 |
7 | cmd-export-ns consul "Consul key-value operations"
8 | cmd-export consul-import
9 | cmd-export consul-export
10 | cmd-export consul-get
11 | cmd-export consul-encoded
12 | cmd-export consul-set
13 | cmd-export consul-ls
14 | cmd-export consul-del
15 | cmd-export consul-info
16 | cmd-export consul-open
17 | cmd-export consul-service
18 | }
19 |
20 | consul-cmd() {
21 | declare path="$1"; shift
22 | local cmd="curl"
23 | if [[ "$CONSUL_AUTH" ]]; then
24 | cmd="curl -u $CONSUL_AUTH"
25 | fi
26 | $cmd --fail -Ss "${CONSUL_URL:?}$path" "$@"
27 | }
28 |
29 | consul-info() {
30 | declare desc="Show all metadata for key"
31 | declare key="${1/#\//}"
32 | consul-cmd "/v1/kv/$key" \
33 | | jq -r .[]
34 | }
35 |
36 | consul-get() {
37 | declare desc="Get the value of key"
38 | declare key="${1/#\//}"
39 | consul-encoded "$key" \
40 | | base64 -d \
41 | | echo "$(cat)"
42 | }
43 |
44 | consul-encoded() {
45 | declare desc="Get the base64 encoded value of key"
46 | declare key="${1/#\//}"
47 | consul-cmd "/v1/kv/$key" \
48 | | jq -r .[].Value
49 | }
50 |
51 | consul-set() {
52 | declare desc="Set the value of key"
53 | declare key="${1/#\//}" value="$2"
54 | consul-cmd "/v1/kv/$key" -X PUT -d "$value" > /dev/null
55 | }
56 |
57 | consul-del() {
58 | declare desc="Delete key"
59 | declare key="${1/#\//}"
60 | consul-cmd "/v1/kv/$key" -X DELETE > /dev/null
61 | }
62 |
63 | consul-ls() {
64 | declare desc="List keys under key"
65 | declare key="${1/#\//}"
66 | if [[ ! "$key" ]]; then
67 | consul-cmd "/v1/kv/?keys&separator=/" \
68 | | jq -r .[] \
69 | | sed 's|/$||'
70 | else
71 | consul-cmd "/v1/kv/$key/?keys&separator=/" \
72 | | jq -r .[] \
73 | | sed "s|$key/||" \
74 | | grep -v ^$ \
75 | | sed 's|/$||'
76 | fi
77 | }
78 |
79 | consul-export() {
80 | declare desc="Export values to Bash variables"
81 | declare key="${1/#\//}"
82 | consul-cmd "/v1/kv/$key/?recurse" \
83 | | jq -r '.[] | [.Key, .Value] | join(" ")' \
84 | | sed "s|$key/||" \
85 | | grep -v '^\s*$' \
86 | | \
87 | while read key value; do
88 | key="${key^^}"
89 | key="${key//\//_}"
90 | printf "$key=%s\n" "$(echo "$value" | base64 -d)"
91 | done
92 | }
93 |
94 | consul-import() {
95 | declare desc="Import Bash variables under key"
96 | declare key="${1/#\//}"
97 | input="$(cat)"
98 | eval "$input" || exit 2
99 | IFS=$'\n'
100 | for line in $input; do
101 | IFS='=' read k v <<< "$line"
102 | IFS=' ' consul-cmd "/v1/kv/${key:?}/$k" -X PUT -d "${!k}" > /dev/null
103 | done
104 | unset IFS
105 | }
106 |
107 | consul-open() {
108 | declare desc="Opens Consul UI in browser (OS X only)"
109 | declare page="$1"
110 | local scheme url
111 | IFS=':' read scheme url <<< "$CONSUL_URL"
112 | open "$scheme://${CONSUL_AUTH}@${url##//}#$page"
113 | }
114 |
115 | consul-service() {
116 | declare service="$1"
117 | consul-cmd "/v1/health/service/$service?passing"
118 | }
119 |
--------------------------------------------------------------------------------
/src/gun.bash:
--------------------------------------------------------------------------------
1 |
2 | readonly latest_version_url="https://dl.gliderlabs.com/glidergun/latest/version.txt"
3 | readonly latest_checksum_url="https://dl.gliderlabs.com/glidergun/latest/%s.tgz.sha256"
4 |
5 | declare GUN_MODULE_DIR="${GUN_MODULE_DIR:-cmds}" # deprecated
6 | declare GUN_PATH="${GUN_PATH:-"$GUN_MODULE_DIR"}"
7 | declare GUN_DIR="${GUN_DIR:-.gun}"
8 |
9 | gun-init() {
10 | declare desc="Initialize a glidergun project directory"
11 | touch Gunfile
12 | if [[ -f .gitignore ]]; then
13 | printf "\n.gun\nGunfile.*\n" >> .gitignore
14 | fi
15 | }
16 |
17 | gun-version() {
18 | declare desc="Display version of glidergun"
19 | local latest="$(curl -s $latest_version_url)"
20 | if [[ "$GUN_VERSION" == "$latest" ]]; then
21 | latest=""
22 | else
23 | latest=" (latest: $latest)"
24 | fi
25 | echo "glidergun $GUN_VERSION$latest"
26 | }
27 |
28 | gun-update() {
29 | declare desc="Self-update glidergun to latest version"
30 | if [[ "$GUN_VERSION" == "$(curl --fail -s $latest_version_url)" ]]; then
31 | echo "glidergun is already up-to-date!"
32 | exit
33 | fi
34 | local platform checksum
35 | platform="$(uname -sm | tr " " "_")"
36 | checksum="$(curl --fail -s $(printf "$latest_checksum_url" "$platform"))"
37 | # calls back into go executable
38 | selfupdate "$platform" "$checksum"
39 | }
40 |
41 | gun-find-root() {
42 | local path="$PWD"
43 | while [[ "$path" != "" && ! -f "$path/Gunfile" ]]; do
44 | path="${path%/*}"
45 | done
46 | if [[ -f "$path/Gunfile" ]]; then
47 | GUN_ROOT="$path"
48 | fi
49 |
50 | if [[ -d "$GUN_ROOT" ]]; then
51 | cd $GUN_ROOT
52 | mkdir -p $GUN_DIR
53 | fi
54 | }
55 |
56 | gun-reload-path() {
57 | local gun_module_paths
58 | IFS=':' read -ra gun_module_paths <<< "$GUN_PATH"
59 | for path in "${gun_module_paths[@]}"; do
60 | module-load-dir "$path"
61 | done
62 | }
63 |
64 | main() {
65 | set -eo pipefail; [[ "$TRACE" ]] && set -x
66 | color-init
67 | gun-find-root
68 |
69 | if [[ "$GUN_ROOT" ]]; then
70 | deps-init
71 | module-load "Gunfile"
72 | if [[ -f "Gunfile.$1" ]]; then
73 | module-load "Gunfile.$1"
74 | GUN_PROFILE="$1"
75 | shift
76 | elif [[ "$GUN_DEFAULT_PROFILE" && -f "Gunfile.$GUN_DEFAULT_PROFILE" ]]; then
77 | module-load "Gunfile.$GUN_DEFAULT_PROFILE"
78 | echo "* Using default profile $GUN_DEFAULT_PROFILE" | >&2 yellow
79 | GUN_PROFILE="$GUN_DEFAULT_PROFILE"
80 | fi
81 | module-load-remote
82 | gun-reload-path
83 | cmd-export env-show :env
84 | cmd-export fn-call ::
85 | else
86 | cmd-export gun-init init
87 | fi
88 |
89 | cmd-export cmd-help :help
90 | cmd-export cmd-bash-complete :complete
91 | cmd-export module-get :get
92 | cmd-export gun-version :version
93 | cmd-export gun-update :update
94 |
95 | if [[ "${!#}" == "-h" || "${!#}" == "--help" ]]; then
96 | local args=("$@")
97 | unset args[${#args[@]}-1]
98 | cmd-ns "" :help "${args[@]}"
99 | elif [[ "${!#}" == "-t" || "${!#}" == "--trace" ]]; then
100 | local args=("$@")
101 | unset args[${#args[@]}-1]
102 | set -x
103 | cmd-ns "" "${args[@]}"
104 | else
105 | cmd-ns "" "$@"
106 | fi
107 | }
108 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 |
4 |
5 | ## [Unreleased][unreleased]
6 | ### Fixed
7 |
8 | ### Added
9 | - `:complete` builtin generates bash autocomplete function: `eval "$(gun :complete)"`
10 |
11 | - Auto-export for module functions starting with `cmd:`
12 | - `:complete` builtin generates bash autocomplete function: `eval "$(gun :complete)"`
13 |
14 | ### Removed
15 |
16 | ### Changed
17 |
18 | ## [0.1.0] - 2015-07-02
19 | ### Fixed
20 | - Resolved issue where `deps-install` download URL has a redirect
21 | - Ensure `gun-find-root` changes working directory to $GUN_ROOT
22 |
23 | ### Added
24 | - `-t` and `--trace` as last argument, enables `-x` closer to command
25 | - Basic test coverage
26 | - Build artifacts on CircleCI, including Go workspace
27 | - Calling help explicitly will show second level commands
28 | - `GUN_PATH` used for module sourcing with `PATH`-like semantics
29 | - Added some initial remote module libraries
30 |
31 | ### Removed
32 |
33 | ### Changed
34 | - Deprecated `GUN_MODULE_PATH` in favor of `GUN_PATH`
35 | - Standard error used for warnings and errors
36 | - Static compilation of binary
37 | - `version` builtin command is now `:version`
38 | - `help` builtin command is now `:help`
39 | - `update` builtin command is now `:update`
40 | - `env` builtin command is now `:env`
41 | - `fn` builtin command is now `::`
42 |
43 | ## [0.0.7] - 2015-02-20
44 | ### Added
45 | - Support for `Gunfile` as global module / profile
46 | - Support for projects without profiles, just `Gunfile`
47 | - Support for `init()` in profiles and `Gunfile`
48 | - Support for `-h` and `--help` as last argument
49 | - `.gun` and `Gunfile.*` added to `.gitignore` on `gun init`
50 |
51 | ### Removed
52 | - Stopped listing second level commands
53 |
54 | ### Changed
55 | - Using `Gunfile` to detect glidergun project instead of `.gun`
56 | - Profiles now use `Gunfile.` instead of `.gun_`
57 |
58 | ## [0.0.6] - 2015-02-16
59 | ### Fixed
60 | - Resolved issue where `deps-require` downloads wrong binary
61 |
62 | ### Changed
63 | - Switched from shunit2 to simpler test system
64 |
65 | ## [0.0.5] - 2015-02-11
66 | ### Fixed
67 | - Avoid use of system `md5` by using baked-in `checksum`
68 | - Ensure `chmod +x` on downloaded binaries in deps.bash
69 | - `gun init` no longer breaks `.gitignore`
70 |
71 | ### Added
72 | - `gun update` which performs a self-update to latest release
73 | - `gun version` also displays latest released version if different
74 |
75 | ### Changed
76 | - `gun init` makes `.gun` with its own `.gitignore`
77 |
78 | ## [0.0.4] - 2015-02-09
79 | ### Fixed
80 | - Use bash from PATH
81 | - Fix single quoting in environment variables
82 |
83 | ## [0.0.3] - 2015-02-09
84 | ### Fixed
85 | - Fixed profile loading logic
86 |
87 | ### Added
88 | - Added basic colored output helpers
89 | - Added `fn-source` function
90 |
91 | ### Removed
92 | - Dropped bindata.go from versioned source
93 |
94 | ### Changed
95 | - Changed `env-export` to `env-import`, now allows default value
96 | - Checksum checking is skipped if index provides none
97 | - `show-env` (`env` subcommand) output is better aligned
98 |
99 | ## [0.0.2] - 2015-02-04
100 | ### Fixed
101 | - Fixed profiles not loading
102 |
103 | [unreleased]: https://github.com/gliderlabs/glidergun/compare/v0.1.0...HEAD
104 | [0.1.0]: https://github.com/gliderlabs/glidergun/compare/v0.0.7...v0.1.0
105 | [0.0.7]: https://github.com/gliderlabs/glidergun/compare/v0.0.6...v0.0.7
106 | [0.0.6]: https://github.com/gliderlabs/glidergun/compare/v0.0.5...v0.0.6
107 | [0.0.5]: https://github.com/gliderlabs/glidergun/compare/v0.0.4...v0.0.5
108 | [0.0.4]: https://github.com/gliderlabs/glidergun/compare/v0.0.3...v0.0.4
109 | [0.0.3]: https://github.com/gliderlabs/glidergun/compare/v0.0.2...v0.0.3
110 | [0.0.2]: https://github.com/gliderlabs/glidergun/compare/v0.0.1...v0.0.2
111 |
--------------------------------------------------------------------------------
/lib/aws/ec2.bash:
--------------------------------------------------------------------------------
1 | # EC2 in VPC helpers and commands
2 |
3 | SSH_HOST=""
4 | SSH_OPTS="${SSH_OPTS:--o PasswordAuthentication=no -o StrictHostKeyChecking=no}"
5 |
6 | init() {
7 | env-import EC2_VPC ""
8 | env-import SSH_USER "core"
9 | env-import SSH_BASTION_USER "$SSH_USER"
10 |
11 | cmd-export-ns ec2 "EC2 instance management"
12 | cmd-export ec2-list
13 | cmd-export ec2-info
14 | cmd-export ec2-ssh
15 | cmd-export ec2-pssh
16 | cmd-export ec2-rsync
17 | cmd-export ec2-prsync
18 | cmd-export ec2-ip
19 | cmd-export ec2-tag
20 | cmd-export ec2-untag
21 | cmd-export ec2-vpcs
22 | cmd-export ec2-terminate
23 | }
24 |
25 | ec2-info() {
26 | declare desc="Instance info by name or ID"
27 | declare instance="$1"
28 | if [[ "${instance:0:2}" != "i-" ]]; then
29 | instance="$(ec2-id instance $instance)"
30 | fi
31 | aws-json ec2 describe-instances --filters "Name=instance-id,Values=$instance" \
32 | | jq '.Reservations[].Instances[] as $instance |
33 | $instance.Tags[] | select(.Key == "Name") | .Value as $name | {
34 | name: $name,
35 | labels: [$instance.Tags[] | select(.Value == null) | .Key],
36 | state: $instance.State.Name,
37 | private_ip: $instance.PrivateIpAddress,
38 | public_ip: $instance.PublicIpAddress,
39 | instance_id: $instance.InstanceId,
40 | subnet_id: $instance.SubnetId }'
41 | }
42 |
43 | ec2-ip() {
44 | declare desc="Public or private IP by ID or tag"
45 | declare type="$1" key="$2" value="$3"
46 | local filter vpc_id
47 | if [[ "${key:0:2}" = "i-" ]]; then
48 | filter="Name=instance-id,Values=$key"
49 | else
50 | filter="Name=tag:$key,Values=$value"
51 | fi
52 | vpc_id="$(ec2-id vpc ${EC2_VPC:?})"
53 | aws-json ec2 describe-instances --filters "Name=vpc-id,Values=$vpc_id" "$filter" \
54 | | jq -r ".Reservations[0].Instances[0].${type^}IpAddress // empty"
55 | }
56 |
57 | ec2-setup-ssh-bastion() {
58 | declare instance="$1"
59 | local bastion_ip
60 | bastion_ip="$(ec2-ip public bastion)"
61 | if [[ "$bastion_ip" ]]; then
62 | SSH_OPTS="$SSH_OPTS -o \"ProxyCommand=ssh -W %h:%p $BASTION_USER@$bastion_ip\""
63 | SSH_HOST="$(ec2-ip private $instance)"
64 | fi
65 | }
66 |
67 | ec2-ssh() {
68 | declare desc="SSH to instance by name or ID"
69 | declare instance="${1:?}"; shift
70 | if [[ "${instance:0:2}" != "i-" ]]; then
71 | instance="$(ec2-id instance $instance)"
72 | fi
73 | ec2-setup-ssh-bastion "$instance"
74 | SSH_HOST="${SSH_HOST:-$(ec2-ip public $instance)}"
75 | local cmd
76 | if [[ "$1" ]]; then
77 | cmd="set -eo pipefail; $@"
78 | fi
79 | eval $(ssh-cmd "$SSH_USER@${SSH_HOST:?}") -- "$(printf '%q' "$cmd")"
80 | }
81 |
82 | ec2-pssh() {
83 | declare desc="Parallel SSH to instances by label"
84 | declare label="$1"; shift
85 | ec2-list "${label:?}" \
86 | | jq -r .[].name \
87 | | parallel "ec2-ssh {} $(printf '%q ' "$@") | sed 's/^/{}: /'"
88 | }
89 |
90 | ec2-rsync() {
91 | declare desc="Rsync files to instance by name or ID"
92 | declare instance="${1:?}" path="$2" remote="$3"
93 | if [[ "${instance:0:2}" != "i-" ]]; then
94 | instance="$(ec2-id instance $instance)"
95 | fi
96 | ec2-setup-ssh-bastion "$instance"
97 | SSH_HOST="${SSH_HOST:-$(ec2-ip public $instance)}"
98 | rsync -rzue "$(ssh-cmd)" \
99 | --rsync-path "mkdir -p $(dirname $remote) && rsync" \
100 | "${path:?}" "$SSH_USER@${SSH_HOST:?}:${remote:?}"
101 | }
102 |
103 | ec2-prsync() {
104 | declare desc="Parallel rsync files to instances by label"
105 | declare label="$1" path="$2" remote="$3"
106 | ec2-list "${label:?}" \
107 | | jq -r .[].name \
108 | | parallel "ec2-rsync {} $path $remote | sed 's/^/{}: /'"
109 | }
110 |
111 | ec2-list() {
112 | declare desc="List instances for a VPC"
113 | declare label="$1"
114 | local vpc_id label_filter
115 | vpc_id="$(ec2-id vpc ${EC2_VPC:?})"
116 | if [[ "$label" ]]; then
117 | label_filter="Name=tag:$label,Values="
118 | fi
119 | aws-json ec2 describe-instances --filters "Name=vpc-id,Values=${vpc_id:?}" "$label_filter" \
120 | | jq '[.Reservations[].Instances[] as $instance |
121 | $instance.Tags[] | select(.Key == "Name") | .Value as $name | {
122 | name: $name,
123 | labels: [$instance.Tags[] | select(.Value == null) | .Key],
124 | private_ip: $instance.PrivateIpAddress,
125 | instance_id: $instance.InstanceId,
126 | subnet_id: $instance.SubnetId }]'
127 | }
128 |
129 | ec2-factory() {
130 | declare id_selector="$1"; shift
131 | aws-json ec2 $@ | jq -e -r "$id_selector"
132 | }
133 |
134 | ec2-tag() {
135 | declare desc="Tag an EC2 resource"
136 | declare resource="$1" key="$2" value="$3"
137 | aws-json ec2 create-tags \
138 | --resources "${resource:?}" \
139 | --tags "Key=${key:?},Value=${value}" > /dev/null
140 | }
141 |
142 | ec2-untag() {
143 | declare desc="Untag an EC2 resource"
144 | declare resource="$1" key="$2"
145 | aws-json ec2 delete-tags \
146 | --resources "${resource:?}" \
147 | --tags "Key=${key:?}" > /dev/null
148 | }
149 |
150 | ec2-id() {
151 | declare type="$1" name="$2"
152 | local json_type filter_name extra_filter
153 | filter_name="tag:Name"
154 | json_type="$(titleize ${type//-/ })"
155 | json_type="${json_type// /}"
156 | json_collection="${json_type}s"
157 | if [[ "$type" = "instance" ]]; then
158 | json_collection="Reservations[0].$json_collection"
159 | extra_filter="Name=instance-state-name,Values=running"
160 | fi
161 | if [[ "$type" = "security-group" ]]; then
162 | filter_name="group-name"
163 | json_type="Group"
164 | fi
165 | aws-json ec2 "describe-${type}s" \
166 | --filters "$extra_filter" "Name=$filter_name,Values=$name" \
167 | | jq -e -r ".${json_collection}[0].${json_type}Id"
168 | }
169 |
170 | ec2-terminate() {
171 | declare desc="Terminate instance by ID"
172 | declare id="$1"
173 | aws-json ec2 terminate-instances --instance-ids "${id:?}"
174 | }
175 |
176 | ec2-vpcs() {
177 | declare desc="List VPCs"
178 | aws-json ec2 describe-vpcs | jq '.Vpcs[] as $vpc |
179 | $vpc.Tags[] | select(.Key == "Name") | .Value as $name | {
180 | name: $name,
181 | id: $vpc.VpcId }'
182 | }
183 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | This tutorial tells how to use [glidergun](https://github.com/gliderlabs/glidergun).
2 | We will discover all important glidergun features step-by-step.
3 |
4 | ## Install Glidergun
5 |
6 | To install glidergun (gun for short):
7 | ```
8 | $ curl -L https://github.com/gliderlabs/glidergun/releases/download/v0.1.0/glidergun_0.1.0_$(uname -sm|tr \ _).tgz \
9 | | tar -zxC /usr/local/bin
10 | ```
11 | ## Initialize a Glidergun project
12 |
13 | Create a new empty directory and enter it.
14 | ```
15 | mkdir /tmp/guntest
16 | cd /tmp/guntest
17 | ```
18 |
19 | gun will search for a file called `Gunfile` starting from $PWD and upwards.
20 | If none found the `init` command will be available.
21 |
22 | ```
23 | $ gun
24 |
25 | Available commands:
26 | init Initialize a glidergun project directory
27 | ```
28 |
29 | Use the `init` command to create an empty `Gunfile` which marks the directory as a valid gun project:
30 | ```
31 | $ gun init
32 | $ ls -la
33 | -rw-r--r-- 1 sillyname wheel 0 Feb 3 09:26 Gunfile
34 | ```
35 | ## Write the first command
36 |
37 | Lets say we want to create a tool for managing github teams. Gun will search the `cmds` directory for files with `*.bash` extension.
38 |
39 | ```
40 | $ mkdir cmds
41 | $ cat > cmds/github.bash <