Skip to content

Commit 9f6705e

Browse files
committed
Merge branch 'release/0.6.0' into stable
2 parents 0dbeb56 + 83bdd5b commit 9f6705e

18 files changed

+524
-122
lines changed

.circleci/config.yml

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
version: 2
2+
general:
3+
branches:
4+
ignore:
5+
- gh-pages
26
jobs:
37
build:
48
machine: true
@@ -7,16 +11,16 @@ jobs:
711
- checkout
812
- run: echo 'export INSTALL_PATH="$HOME/dependencies";export PATH="$INSTALL_PATH/bin:$PATH";export MIX_ENV=test;export VERSION_CIRCLECI=2' >> $BASH_ENV
913
- restore_cache:
10-
key: environment-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum "script/ci/prepare.sh" }}
14+
key: environment-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum "script/ci/prepare.sh" }}-{{ arch }}
1115
- run:
1216
name: Install Elixir
1317
command: script/ci/prepare.sh
1418
- save_cache:
15-
key: environment-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum "script/ci/prepare.sh" }}
19+
key: environment-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum "script/ci/prepare.sh" }}-{{ arch }}
1620
paths:
1721
- ~/dependencies
1822
- restore_cache:
19-
key: dependencies-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum "mix.lock" }}
23+
key: dependencies-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum "mix.lock" }}-{{ arch }}
2024
- run:
2125
name: Preparing dependencies
2226
command: |
@@ -27,7 +31,7 @@ jobs:
2731
mix dialyzer --plt;
2832
no_output_timeout: 10m
2933
- save_cache:
30-
key: dependencies-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum "mix.lock" }}
34+
key: dependencies-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum "mix.lock" }}-{{ arch }}
3135
paths:
3236
- ~/.mix
3337
- _build

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ erl_crash.dump
1818

1919
# Also ignore archive artifacts (built via "mix archive.build").
2020
*.ez
21+
/_site
22+
/.sass-cache

README.md

+48-30
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@
44

55
Simple deployment and server automation for Elixir.
66

7-
**Bootleg** is a simple set of commands that attempt to simplify building and deploying elixir applications. The goal of the project is to provide an extensible framework that can support many different deploy scenarios with one common set of commands.
7+
**Bootleg** is a simple set of commands that attempt to simplify building and deploying Elixir applications. The goal of the project is to provide an extensible framework that can support many different deployment scenarios with one common set of commands.
88

9-
Out of the box, Bootleg provides remote build and remote server automation for your existing [Distillery](https://github.com/bitwalker/distillery) releases. Bootleg assumes your project is committed into a `git` repository and some of the build steps use this assumption
10-
to handle code in some steps of the build process. If you are using an scm other than git, please consider contributing to Bootleg to
9+
Out of the box, Bootleg provides remote build and remote server automation for your [Distillery](https://github.com/bitwalker/distillery) releases. Bootleg assumes your project is committed into a **git** repository and some of the build steps use this assumption
10+
to handle code within the build process. If you are using another source control management (SCM) tool please consider contributing to Bootleg to
1111
add additional support.
1212

1313
## Installation
1414

15-
```elixir
15+
```
1616
def deps do
17-
[{:distillery, "~> 1.3",
18-
{:bootleg, "~> 0.5"}]
17+
[{:distillery, "~> 1.5",
18+
{:bootleg, "~> 0.6"}]
1919
end
2020
```
2121

@@ -24,7 +24,6 @@ end
2424
In order to build your project, Bootleg requires that your build server be set up to compile
2525
Elixir code. Make sure you have already installed Elixir on any build server you define.
2626

27-
2827
## Quick Start
2928

3029
### Initialize your project
@@ -138,6 +137,7 @@ by Bootleg:
138137
* `password` - ssh password
139138
* `identity` - unencrypted private key file path (passphrases are not supported at this time)
140139
* `port` - ssh port (default `22`)
140+
* `replace_os_vars` - controls the `REPLACE_OS_VARS` environment variable used by Distillery for release configuration (default `true`)
141141

142142
#### Examples
143143

@@ -395,29 +395,50 @@ end
395395

396396
## Phoenix Support
397397

398-
Bootleg builds elixir apps, if your application has extra steps required make use of the hooks
398+
If your application has extra steps required, you may make use of the hooks
399399
system to add additional functionality. A common case is for building assets for Phoenix
400-
applications. To build phoenix assets during your build, include the additional package
401-
`bootleg_phoenix` to your `deps` list. This will automatically perform the additional steps required
402-
for building phoenix releases.
400+
applications.
401+
402+
### Using the bootleg_phoenix package
403+
404+
To run these steps automatically you may include the additional package
405+
`bootleg_phoenix` in your `deps` list. This package provides the build hook commands required to build most Phoenix releases.
403406

404407
```elixir
405408
# mix.exs
406409
def deps do
407-
[{:distillery, "~> 1.3"},
408-
{:bootleg, "~> 0.5"},
409-
{:bootleg_phoenix, "~> 0.1"}]
410+
[{:distillery, "~> 1.5"},
411+
{:bootleg, "~> 0.6"},
412+
{:bootleg_phoenix, "~> 0.2"}]
410413
end
411414
```
412415

413-
For more about `bootleg_phoenix` see: https://github.com/labzero/bootleg_phoenix
416+
See also: [labzero/bootleg_phoenix](https://github.com/labzero/bootleg_phoenix).
414417

415-
## Sharing Tasks
418+
### Using your own deploy configuration and hooks
416419

417-
Sharing is a good thing. We love to share, especially awesome code we write. Bootleg supports loading
418-
tasks from packages in a manner very similar to `Mix.Task`. Just define your module under `Bootleg.Tasks`,
419-
`use Bootleg.Task` and pass it a block of Bootleg DSL. The contents will be discovered and executed
420-
automatically at launch.
420+
Similar to how `bootleg_phoenix` is implemented, you can make use of the hooks system to run some commands on the build server around compile time.
421+
422+
```elixir
423+
task :phoenix_digest do
424+
remote :build do
425+
"npm install"
426+
"./node_modules/brunch/bin/brunch b -p"
427+
"MIX_ENV=prod mix phoenix.digest"
428+
end
429+
UI.info "Phoenix asset digest generated"
430+
end
431+
432+
after_task :compile, :phoenix_digest
433+
```
434+
435+
436+
## Task Providers
437+
438+
Sharing is a good thing. Bootleg supports loading
439+
tasks from packages in a manner very similar to `Mix.Task`.
440+
441+
You can create and share custom tasks by namespacing a module under `Bootleg.Tasks` and passing a block of Bootleg DSL:
421442

422443
```elixir
423444
defmodule Bootleg.Tasks.Foo do
@@ -431,30 +452,27 @@ defmodule Bootleg.Tasks.Foo do
431452
end
432453
```
433454

434-
See `Bootleg.Task` for more details.
455+
See also: [Bootleg.Task](https://hexdocs.pm/bootleg/Bootleg.Task.html#content) for additional examples.
435456

436457
## Help
437458

438-
If something goes wrong, retry with the `--verbose` option.
439459
For detailed information about the Bootleg commands and their options, try `mix bootleg help <command>`.
440460

461+
We're usually around on Slack where you can find us on [elixir-lang's #bootleg channel](http://elixir-lang.slack.com/messages/bootleg/) if you have any questions.
462+
441463
-----
442464

443465
## Acknowledgments
444466

445467
Bootleg makes heavy use of the [bitcrowd/SSHKit.ex](https://github.com/bitcrowd/sshkit.ex)
446-
library under the hood. We would like to acknowledge the effort from the bitcrowd team that went into
447-
creating SSHKit.ex as well as for them prioritizing our requests and providing a chance to collaborate
448-
on ideas for both the SSHKit.ex and Bootleg projects.
468+
library under the hood. We are very appreciative of the efforts of the bitcrowd team for both creating SSHKit.ex and being so attentive to our requests. We're also grateful for the opportunity to collaborate
469+
on ideas for both projects!
449470

450471
## Contributing
451472

452-
We welcome everyone to contribute to Bootleg and help us tackle existing issues!
453-
454-
Use the [issue tracker][issues] for bug reports or feature requests.
455-
Open a [pull request][pulls] when you are ready to contribute.
473+
We welcome all contributions to Bootleg, whether they're improving the documentation, implementing features, reporting issues or suggesting new features.
456474

457-
If you are planning to contribute documentation, please check
475+
If you'd like to contribute documentation, please check
458476
[the best practices for writing documentation][writing-docs].
459477

460478

lib/bootleg/config.ex

+103-5
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ defmodule Bootleg.Config do
88

99
defmacro __using__(_) do
1010
quote do
11-
import Bootleg.Config, only: [role: 2, role: 3, config: 2, config: 0, before_task: 2,
12-
after_task: 2, invoke: 1, task: 2, remote: 1, remote: 2, remote: 3, load: 1, upload: 3]
11+
import Bootleg.Config, only: [role: 2, role: 3, config: 2, config: 1, config: 0,
12+
before_task: 2, after_task: 2, invoke: 1, task: 2, remote: 1, remote: 2,
13+
remote: 3, load: 1, upload: 3, download: 3]
1314
end
1415
end
1516

@@ -51,7 +52,14 @@ defmodule Bootleg.Config do
5152
|> Keyword.put(:user, user)
5253
# identity needs to be present in both options lists
5354
|> Keyword.put(:identity, ssh_options[:identity])
54-
|> Enum.filter(fn {_, v} -> v end)
55+
|> Keyword.get_and_update(:identity, fn val ->
56+
if val || Keyword.has_key?(ssh_options, :identity) do
57+
{val, val || ssh_options[:identity]}
58+
else
59+
:pop
60+
end
61+
end)
62+
|> elem(1)
5563

5664
quote bind_quoted: binding() do
5765
hosts =
@@ -94,6 +102,42 @@ defmodule Bootleg.Config do
94102
end
95103
end
96104

105+
@doc """
106+
Fetches the value for the supplied key from the Bootleg configuration. If the provided
107+
key is a `Tuple`, the first element is considered the key, the second value is considered
108+
the default value (and returned without altering the config) in case the key has not
109+
been set. This uses the same semantics as `Keyword.get/3`.
110+
111+
```
112+
use Bootleg.Config
113+
config :foo, :bar
114+
115+
# local_foo will be :bar
116+
local_foo = config :foo
117+
118+
# local_foo will be :bar still, as :foo already has a value
119+
local_foo = config {:foo, :car}
120+
121+
# local_hello will be :world, as :hello has not been defined yet
122+
local_hello = config {:hello, :world}
123+
124+
config :hello, nil
125+
# local_hello will be nil, as :hello has a value of nil now
126+
local_hello = config {:hello, :world}
127+
```
128+
"""
129+
defmacro config({key, default}) do
130+
quote bind_quoted: binding() do
131+
Keyword.get(Bootleg.Config.Agent.get(:config), key, default)
132+
end
133+
end
134+
135+
defmacro config(key) do
136+
quote bind_quoted: binding() do
137+
Keyword.get(Bootleg.Config.Agent.get(:config), key)
138+
end
139+
end
140+
97141
@doc """
98142
Sets `key` in the Bootleg configuration to `value`.
99143
@@ -106,6 +150,7 @@ defmodule Bootleg.Config do
106150
107151
config :app, :my_cool_app
108152
config :version, "1.0.0"
153+
```
109154
"""
110155
defmacro config(key, value) do
111156
quote bind_quoted: binding() do
@@ -399,10 +444,10 @@ defmodule Bootleg.Config do
399444
# runs for hosts found in :build first, then for hosts in :app
400445
remote [:build, :app], do: "hostname"
401446
402-
# only runs on `host1.example.com`
403447
role :build, "host2.example.com"
404-
role :build, "host1.example.com", filter: [primary: true, another_attr: :cat]
448+
role :build, "host1.example.com", primary: true, another_attr: :cat
405449
450+
# only runs on `host1.example.com`
406451
remote :build, filter: [primary: true] do
407452
"hostname"
408453
end
@@ -491,6 +536,59 @@ defmodule Bootleg.Config do
491536
end
492537
end
493538

539+
@doc """
540+
Downloads files from remote hosts to the local machine.
541+
542+
Downloading works much like `remote/3`, but instead of transferring shell commands over SSH,
543+
it transfers files via SCP. The remote host does need to support SCP, which should be provided
544+
by most SSH implementations automatically.
545+
546+
`role` can either be a single role name, a list of roles, or a list of roles and filter
547+
attributes. The special `:all` role is also supported. See `remote/3` for details. Note that
548+
if multiple hosts match, files will be downloaded from all matching hosts, and any duplicate
549+
file names will result in collisions. The exact semantics of how that works are handled by
550+
`SSHKit.SCP`, but in general the file transfered last wins.
551+
552+
`local_path` is a path to local directory or file where the downloaded files(s) should be placed.
553+
Absolute paths will be respected, relative paths will be resolved relative to the current working
554+
directory of the invoking shell. If the `local_path` does not exist in the local file system, an
555+
attempt will be made to create the missing directory. This does not handle nested directories,
556+
and a `File.Error` will be raised.
557+
558+
`remote_path` is the file or directory to be copied from the remote hosts. If a directory is
559+
specified, its contents will be recursively copied. Relative paths will be resolved relative to
560+
the remote workspace, absolute paths will be respected.
561+
562+
The files on the local host are created using the current user's `uid`/`gid` and `umask`.
563+
564+
```
565+
use Bootleg.Config
566+
567+
# copies ./my_file from the remote host to ./new_name locally
568+
download :app, "my_file", "new_name"
569+
570+
# copies ./my_file from the remote host to the file ./a_dir/my_file locally
571+
download :app, "my_file", "a_dir"
572+
573+
# recursively copies ./some_dir on the remote host to ./new_dir locally, ./new_dir
574+
# will be created if missing
575+
download :app, "some_dir", "new_dir"
576+
577+
# copies /foo/my_file on the remote host to /tmp/foo locally
578+
download :app, "/foo/my_file", "/tmp/foo"
579+
"""
580+
defmacro download(role, remote_path, local_path) do
581+
{roles, filters} = split_roles_and_filters(role)
582+
roles = unpack_role(roles)
583+
quote bind_quoted: binding() do
584+
Enum.each(roles, fn role ->
585+
role
586+
|> SSH.init([], filters)
587+
|> SSH.download(remote_path, local_path)
588+
end)
589+
end
590+
end
591+
494592
@doc false
495593
@spec get_config(atom, any) :: any
496594
def get_config(key, default \\ nil) do

0 commit comments

Comments
 (0)