beam_up 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +124 -0
- data/lib/beam_up/cli.rb +187 -0
- data/lib/beam_up/configuration.rb +48 -0
- data/lib/beam_up/core.rb +68 -0
- data/lib/beam_up/errors.rb +7 -0
- data/lib/beam_up/providers/aws_s3.rb +44 -0
- data/lib/beam_up/providers/base.rb +25 -0
- data/lib/beam_up/providers/bunny.rb +78 -0
- data/lib/beam_up/providers/digital_ocean_spaces.rb +46 -0
- data/lib/beam_up/providers/hetzner.rb +42 -0
- data/lib/beam_up/providers/neocities.rb +72 -0
- data/lib/beam_up/providers/netlify.rb +125 -0
- data/lib/beam_up/providers/s3_compatible.rb +63 -0
- data/lib/beam_up/providers/sftp.rb +81 -0
- data/lib/beam_up/providers/statichost.rb +87 -0
- data/lib/beam_up/providers/transporter.rb +54 -0
- data/lib/beam_up/providers.rb +17 -0
- data/lib/beam_up/result.rb +26 -0
- data/lib/beam_up/version.rb +3 -0
- data/lib/beam_up.rb +31 -0
- metadata +108 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 55ab9d14c6fe9bb0bdb38f023f1242f174b538b117c5d0f95fabe6231ad6f745
|
|
4
|
+
data.tar.gz: 3859429f3775c52781da38cf040ae2cbd4d246a7062255cd0aaee0dd79a8e0c9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5f4e221e74c4661c0df1cc5d1287ee69bb6e025762fd8a9e4df1e4739629219195e547cc7742e1b9420ac35d15fcb0208f060f5541a535198bc9eba9fdfb4530
|
|
7
|
+
data.tar.gz: 4e51baca30f01370cdd292fd3f4ee0bd47dbb67c4800c7416f1b52a644bcd08a535afd0587cd075ccc7180c0fddba2c50c09789d71e37624c00e7bfa1e8bc8da
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rails Designer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Beam Up
|
|
2
|
+
|
|
3
|
+
Static site deployment CLI supporting multiple providers. Just run `beam_up ./output`.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
If you have a Ruby environment available, you can install:
|
|
9
|
+
```bash
|
|
10
|
+
gem install beam_up
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Configuration
|
|
15
|
+
|
|
16
|
+
Create a config file:
|
|
17
|
+
```bash
|
|
18
|
+
beam_up init netlify
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or manually create `.beam_up.yml` (or `config/beam_up.yml`):
|
|
22
|
+
```yaml
|
|
23
|
+
provider: netlify
|
|
24
|
+
# path: ./output # optional
|
|
25
|
+
netlify:
|
|
26
|
+
api_token: your_token_here
|
|
27
|
+
project_id: your_project_id
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If both files exist, `config/beam_up.yml` takes priority.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## Deploy
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Deploy using configured provider
|
|
37
|
+
beam_up ./output
|
|
38
|
+
|
|
39
|
+
# Override provider for this deploy
|
|
40
|
+
beam_up ./output --to bunny
|
|
41
|
+
# or
|
|
42
|
+
beam_up ./output --provider bunny
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## Supported providers
|
|
47
|
+
|
|
48
|
+
- aws_s3
|
|
49
|
+
- bunny
|
|
50
|
+
- digital_ocean_spaces
|
|
51
|
+
- hetzner
|
|
52
|
+
- neocities
|
|
53
|
+
- netlify
|
|
54
|
+
- sftp
|
|
55
|
+
- statichost
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## Usage from Ruby
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
require "beam_up"
|
|
62
|
+
|
|
63
|
+
BeamUp.deploy! "./output"
|
|
64
|
+
|
|
65
|
+
# Deploy with provider override
|
|
66
|
+
BeamUp.deploy! "./output", to: "bunny"
|
|
67
|
+
# or
|
|
68
|
+
BeamUp.deploy! "./output", provider: "bunny"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
## Hooks
|
|
73
|
+
|
|
74
|
+
Run commands before and after deployment:
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
provider: netlify
|
|
78
|
+
before_actions:
|
|
79
|
+
- npm run build
|
|
80
|
+
after_actions:
|
|
81
|
+
- echo "Deployment complete ✨"
|
|
82
|
+
netlify:
|
|
83
|
+
api_token: your_token_here
|
|
84
|
+
project_id: your_project_id
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
If a `before_action` fails, deployment is cancelled.
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
## SFTP
|
|
91
|
+
|
|
92
|
+
Beam Up supports SFTP like the good ol' days. Requires additional gems:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
gem install net-ssh net-sftp
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Configuration:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
provider: sftp
|
|
102
|
+
sftp:
|
|
103
|
+
host: your-server.com
|
|
104
|
+
username: deploy_user
|
|
105
|
+
remote_path: /var/www/html
|
|
106
|
+
# Use either password OR key:
|
|
107
|
+
password: your_password
|
|
108
|
+
# key: ~/.ssh/id_rsa
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## Contributing
|
|
113
|
+
|
|
114
|
+
This project uses [Standard](https://github.com/testdouble/standard) for formatting. Run `rake` before submitting pull requests.
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT License
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
*Fun fact: “Beam me up, Scotty” was never spoken in any Star Trek television episode or film.*
|
data/lib/beam_up/cli.rb
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
require "yaml"
|
|
3
|
+
|
|
4
|
+
module BeamUp
|
|
5
|
+
class CLI
|
|
6
|
+
def self.start(arguments)
|
|
7
|
+
new(arguments).run
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(arguments)
|
|
11
|
+
@arguments = arguments
|
|
12
|
+
@provider = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run
|
|
16
|
+
command = @arguments.shift
|
|
17
|
+
|
|
18
|
+
case command
|
|
19
|
+
when "init" then init
|
|
20
|
+
when "scotty" then scotty
|
|
21
|
+
when "--help", "-h" then help
|
|
22
|
+
when nil then deploy_or_help
|
|
23
|
+
else
|
|
24
|
+
@arguments.unshift(command)
|
|
25
|
+
|
|
26
|
+
deploy
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def init
|
|
31
|
+
provider_name = @arguments.shift&.downcase
|
|
32
|
+
|
|
33
|
+
if provider_name.nil? || !PROVIDERS.key?(provider_name)
|
|
34
|
+
puts "Available providers:"
|
|
35
|
+
|
|
36
|
+
PROVIDERS.keys.reject { it == "transporter" }.sort.each { puts " - #{it}" }
|
|
37
|
+
|
|
38
|
+
exit(1)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
provider_config_class = PROVIDERS[provider_name]::Config
|
|
42
|
+
config_keys = provider_config_class.config_keys
|
|
43
|
+
config_file = ["config/beam_up.yml", ".beam_up.yml"].find { File.exist?(it) }
|
|
44
|
+
|
|
45
|
+
if config_file
|
|
46
|
+
existing_config = YAML.safe_load_file(config_file) || {}
|
|
47
|
+
|
|
48
|
+
if existing_config.key?(provider_name)
|
|
49
|
+
puts "Provider '#{provider_name}' already configured in #{config_file}"
|
|
50
|
+
|
|
51
|
+
exit(1)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
provider_section = YAML.dump({provider_name => config_keys.to_h { [it, ""] }}, indent: 2, line_width: 80).sub(/^---\n/, "")
|
|
55
|
+
File.write(config_file, File.read(config_file) + "\n" + provider_section)
|
|
56
|
+
|
|
57
|
+
puts "Updated #{config_file} with #{provider_name} provider"
|
|
58
|
+
else
|
|
59
|
+
configuration = YAML.dump({
|
|
60
|
+
"provider" => provider_name,
|
|
61
|
+
"path" => nil,
|
|
62
|
+
provider_name => config_keys.to_h { |key| [key, ""] }
|
|
63
|
+
}, indent: 2, line_width: 80).gsub(/^path:$/, "# path: ./output # uncomment to set a default folder")
|
|
64
|
+
|
|
65
|
+
File.write(".beam_up.yml", configuration)
|
|
66
|
+
puts "Created .beam_up.yml with #{provider_name} provider"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def deploy_or_help
|
|
71
|
+
if File.exist?(".beam_up.yml") || File.exist?("config/beam_up.yml")
|
|
72
|
+
deploy
|
|
73
|
+
else
|
|
74
|
+
puts "No .beam_up.yml found. Run `beam_up init PROVIDER` to get started"
|
|
75
|
+
puts
|
|
76
|
+
|
|
77
|
+
help
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def deploy
|
|
82
|
+
input = parse_options
|
|
83
|
+
|
|
84
|
+
beamed = BeamUp.deploy! input, provider: @provider
|
|
85
|
+
|
|
86
|
+
puts beamed.message
|
|
87
|
+
puts "Deploy ID: #{beamed.deploy_id}" if beamed.deploy_id
|
|
88
|
+
|
|
89
|
+
exit(1) unless beamed.success?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def scotty
|
|
93
|
+
puts <<~STARFLEET
|
|
94
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*+=%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
95
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%***++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
96
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%***+++=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
97
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%**+++++++%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
98
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#*+++++++++-%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
99
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@***+*++++++++:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
100
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%****++++++++++=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
101
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@***+*++++++++++++=%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
102
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@%***+++++++++++++++=%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
103
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@*****+++++++++++++++=%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
104
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@%****+++++++++++++++++=%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
105
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@%+****+++++++++++++++++++=%@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
106
|
+
@@@@@@@@@@@@@@@@@@@@%%=##%***+++++++++++++++++++++==++*-%%@@@@@@@@@@@@@@@@@@@@@@
|
|
107
|
+
@@@@@@@@@@@@@@%%%#*+-=#*%%*++++++++++++++++++++++++==*+*#.++**@@@@@@@@@@@@@@@@@@
|
|
108
|
+
@@@@@@@@@@@@%-*+.#%%#####**++++++++++++++++++++++++++=+++++++*%+*%@@@@@@@@@@@@@@
|
|
109
|
+
@@@@@@@@%%%*+=#%%%%#%%%%-*+++++++++++++++++++++++++++===++++=+==*:=**@@@@@@@@@@@
|
|
110
|
+
@@@@@@%%*+-#%%%%%%%%###%*++++++++++++++++++++++++++++==-=++++++====+.+:%@@@@@@@@
|
|
111
|
+
@@@@@%*+-#%%%%%%%%#%#%#=+++++++++++++++++++++++++++++===:=++++++======*==%@@@@@@
|
|
112
|
+
@@@@%*:###%%%%%######%%++++++++++++++++++++++++++++======:++++++========*=:%@@@@
|
|
113
|
+
@@%%+-###############%-++++++++++++++++++++++++++++=======.+++++++========:*%@@@
|
|
114
|
+
@@#+=#################++++++++++++++++++++++++++++++=======-++++++=+=======:*%@@
|
|
115
|
+
@%+-**##############%=+++++++++++++++++++++++++++++=========+++++++++=======:*@@
|
|
116
|
+
%*:*******##########%=++++++++++++++++++++++++++============.+++++++=========.=%
|
|
117
|
+
**:**+********#####*#+++++++++++++++++++++++++=============== +++++++=========*%
|
|
118
|
+
=*=++++++**********#.++++++++++++++++++++++++================-=+++++++=======**:
|
|
119
|
+
+*.+====++++*******#++++++++++++++++++++======================.+++++++=======++:
|
|
120
|
+
*#:==-====++++++++-.+++++++++++++==+===========================:++++=========+*=
|
|
121
|
+
%-*+----=====+++++- ++++++=====================================:=++==========%+%
|
|
122
|
+
@*:*#----=======+=*==========================-++++#.============-===========#*=%
|
|
123
|
+
@%=:**=---=====+=-#======================-:+++++++++:-=====================*#-@@
|
|
124
|
+
@@%==*.+---======..=====================:+++++++++++++..=========.========%*-@@@
|
|
125
|
+
@@@@%--*-=--=====. ===================:++++++++++++++++.:========-======+%+%@@@@
|
|
126
|
+
@@@@@%=--*-======-.=================:+++++++++++++++++++*.+=======.===+**=%@@@@@
|
|
127
|
+
@@@@@@@%#- #:*==:%-=================++++++++++++++++++++++ -======-=*%+=%%@@@@@@
|
|
128
|
+
@@@@@@@@@@%-=:#:% ==============-:+++++++++++++++++++++++++.:====== -%@@@@@@@@@@
|
|
129
|
+
@@@@@@@@@@@@@%---.=============:++++++++++++++++++++++++++++ +======%@@@@@@@@@@@
|
|
130
|
+
@@@@@@@@@@@@@@@%-.=============+++++++++++++++++++++++++#**#*--===== @@@@@@@@@@@
|
|
131
|
+
@@@@@@@@@@@@@@@%=:==========.%%+.:-%*****+*+**#*---%#*+--::*%::-====-%@@@@@@@@@@
|
|
132
|
+
@@@@@@@@@@@@@@%* :=========*#:::::-=+-::-===+=-:::::+%@@@@@@@%.:-====.@@@@@@@@@@
|
|
133
|
+
@@@@@@@@@@@@@@@= -=======:%%@@@@@%%%%%%%%%@@@@@@@@@@@@@@@@@@@@@::.===-%@@@@@@@@@
|
|
134
|
+
@@@@@@@@@@@@@@@= ======:%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=:--==+@@@@@@@@@
|
|
135
|
+
@@@@@@@@@@@@@@@=.=====-%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=-.==-@@@@@@@@@
|
|
136
|
+
@@@@@@@@@@@@@@%=:====:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@--+==%@@@@@@@@
|
|
137
|
+
@@@@@@@@@@@@@@%=:==:%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%-===%@@@@@@@@
|
|
138
|
+
@@@@@@@@@@@@@@@=:=:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-:-@@@@@@@@@
|
|
139
|
+
@@@@@@@@@@@@@@%-:+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
140
|
+
STARFLEET
|
|
141
|
+
exit
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
private
|
|
145
|
+
|
|
146
|
+
def help
|
|
147
|
+
puts <<~HELP
|
|
148
|
+
Usage:
|
|
149
|
+
beam_up init PROVIDER # Initialize config file for provider
|
|
150
|
+
beam_up [FOLDER] # Deploy using .beam_up.yml config
|
|
151
|
+
beam_up [FOLDER] --to PROVIDER # Deploy with provider override
|
|
152
|
+
beam_up [FOLDER] --provider PROVIDER # Alias for --to
|
|
153
|
+
|
|
154
|
+
Examples:
|
|
155
|
+
beam_up init netlify
|
|
156
|
+
beam_up ./output
|
|
157
|
+
beam_up ./output --to aws_s3
|
|
158
|
+
HELP
|
|
159
|
+
|
|
160
|
+
exit
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def parse_options
|
|
164
|
+
if @arguments.first && !@arguments.first.start_with?("--")
|
|
165
|
+
input = @arguments.shift
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
OptionParser.new do |options|
|
|
169
|
+
options.on("--provider PROVIDER", "Override the provider from config") do |value|
|
|
170
|
+
@provider = value
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
options.on("--to PROVIDER", "Alias for --provider") do |value|
|
|
174
|
+
@provider = value
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
options.on("--help", "-h", "Show this help") do
|
|
178
|
+
help
|
|
179
|
+
|
|
180
|
+
exit
|
|
181
|
+
end
|
|
182
|
+
end.parse!(@arguments)
|
|
183
|
+
|
|
184
|
+
input
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BeamUp
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :provider, :path, :before_actions, :after_actions, :timeout
|
|
6
|
+
|
|
7
|
+
DEFAULT_TIMEOUT = 300 # 5 minutes
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@providers = {}
|
|
11
|
+
@timeout = DEFAULT_TIMEOUT
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.with(options)
|
|
15
|
+
new.tap do |config|
|
|
16
|
+
config.provider = options[:provider]
|
|
17
|
+
config.provider_config.with(options)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def provider_config
|
|
22
|
+
raise(ConfigurationError, "Provider must be set") if provider.nil? || provider.empty?
|
|
23
|
+
|
|
24
|
+
provider_class = PROVIDERS[provider.to_s]
|
|
25
|
+
raise(ConfigurationError, "Unknown provider: #{provider}") unless provider_class
|
|
26
|
+
|
|
27
|
+
@providers[provider.to_s] ||= provider_class::Config.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def validate!
|
|
31
|
+
raise ConfigurationError, "Provider must be set" unless provider
|
|
32
|
+
|
|
33
|
+
provider_config.validate!
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def method_missing(method_name, *arguments)
|
|
37
|
+
if PROVIDERS.key?(method_name.to_s)
|
|
38
|
+
@providers[method_name.to_s] ||= PROVIDERS[method_name.to_s]::Config.new
|
|
39
|
+
else
|
|
40
|
+
super
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
45
|
+
PROVIDERS.key?(method_name.to_s) || super
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/beam_up/core.rb
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module BeamUp
|
|
6
|
+
class Core
|
|
7
|
+
class << self
|
|
8
|
+
def configure
|
|
9
|
+
yield(configuration)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def configuration
|
|
13
|
+
@configuration ||= configuration_file || raise(ConfigurationError, "No .beam_up.yml found. Run `beam_up init PROVIDER` to create one.")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def deploy!(path = nil, provider: nil)
|
|
17
|
+
config = configuration_file
|
|
18
|
+
config.provider = provider if provider
|
|
19
|
+
|
|
20
|
+
deploy_path = path || config.path || raise(ConfigurationError, "No path specified")
|
|
21
|
+
|
|
22
|
+
config.validate!
|
|
23
|
+
|
|
24
|
+
execute! config.before_actions
|
|
25
|
+
|
|
26
|
+
provider_class = PROVIDERS[config.provider.to_s] || raise(ConfigurationError, "Unknown provider: #{config.provider}")
|
|
27
|
+
result = provider_class.new(config.provider_config).deploy! deploy_path
|
|
28
|
+
|
|
29
|
+
execute! config.after_actions
|
|
30
|
+
|
|
31
|
+
result
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def configuration_file
|
|
37
|
+
file = ["config/beam_up.yml", ".beam_up.yml"].find { File.exist?(it) }
|
|
38
|
+
return nil unless file
|
|
39
|
+
|
|
40
|
+
data = YAML.safe_load_file(file)
|
|
41
|
+
|
|
42
|
+
Configuration.new.tap do |config|
|
|
43
|
+
config.provider = data["provider"]
|
|
44
|
+
config.path = data["path"]
|
|
45
|
+
config.timeout = data["timeout"] || Configuration::DEFAULT_TIMEOUT
|
|
46
|
+
config.before_actions = data["before_actions"] || []
|
|
47
|
+
config.after_actions = data["after_actions"] || []
|
|
48
|
+
|
|
49
|
+
PROVIDERS.each_key do |provider_name|
|
|
50
|
+
if (provider_data = data[provider_name])
|
|
51
|
+
config.send(provider_name).with(provider_data.transform_keys(&:to_sym))
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def execute!(actions)
|
|
58
|
+
return if actions.nil? || actions.empty?
|
|
59
|
+
|
|
60
|
+
actions.each do |action|
|
|
61
|
+
result = system(action)
|
|
62
|
+
|
|
63
|
+
raise ConfigurationError, "Before action failed: #{action}" unless result
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "beam_up/providers/s3_compatible"
|
|
4
|
+
|
|
5
|
+
module BeamUp
|
|
6
|
+
module Providers
|
|
7
|
+
class AwsS3 < S3Compatible
|
|
8
|
+
class Config
|
|
9
|
+
def self.config_keys = %w[access_key secret_key region bucket url]
|
|
10
|
+
|
|
11
|
+
attr_accessor :access_key, :secret_key, :region, :bucket, :url
|
|
12
|
+
|
|
13
|
+
def with(options)
|
|
14
|
+
self.access_key = options[:access_key]
|
|
15
|
+
self.secret_key = options[:secret_key]
|
|
16
|
+
self.region = options[:region] || "us-east-1"
|
|
17
|
+
self.bucket = options[:bucket]
|
|
18
|
+
self.url = options[:url]
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def validate!
|
|
23
|
+
raise ConfigurationError, "Access key must be set" unless access_key
|
|
24
|
+
raise ConfigurationError, "Secret key must be set" unless secret_key
|
|
25
|
+
raise ConfigurationError, "Bucket must be set" unless bucket
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def bucket_name = @configuration.bucket
|
|
32
|
+
|
|
33
|
+
def endpoint
|
|
34
|
+
"https://s3.#{@configuration.region}.amazonaws.com"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def public_url
|
|
38
|
+
@configuration.url || "https://#{bucket_name}.s3.#{@configuration.region}.amazonaws.com"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def provider_name = "AWS S3"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "beam_up/result"
|
|
4
|
+
|
|
5
|
+
module BeamUp
|
|
6
|
+
module Providers
|
|
7
|
+
class Base
|
|
8
|
+
def initialize(configuration)
|
|
9
|
+
@configuration = configuration
|
|
10
|
+
|
|
11
|
+
configuration.validate!
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def deploy!
|
|
15
|
+
raise NotImplementedError, "Subclasses must implement #deploy"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def files_to_deploy
|
|
21
|
+
Dir.glob("#{@path}/**/*").select { File.file?(it) }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "digest"
|
|
5
|
+
|
|
6
|
+
module BeamUp
|
|
7
|
+
module Providers
|
|
8
|
+
class Bunny < Base
|
|
9
|
+
class Config
|
|
10
|
+
def self.config_keys = %w[storage_zone_password storage_zone_name region]
|
|
11
|
+
|
|
12
|
+
attr_accessor :storage_zone_password, :storage_zone_name, :region
|
|
13
|
+
|
|
14
|
+
def with(options)
|
|
15
|
+
self.storage_zone_password = options[:storage_zone_password]
|
|
16
|
+
self.storage_zone_name = options[:storage_zone_name]
|
|
17
|
+
self.region = options[:region] || "de"
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def validate!
|
|
22
|
+
raise ConfigurationError, "Storage zone password must be set" unless storage_zone_password
|
|
23
|
+
raise ConfigurationError, "Storage zone name must be set" unless storage_zone_name
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def deploy!(path)
|
|
28
|
+
@path = path
|
|
29
|
+
|
|
30
|
+
files_to_deploy.each do |file|
|
|
31
|
+
upload file
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Result.new(
|
|
35
|
+
provider: "Bunny",
|
|
36
|
+
deploy_id: Time.now.to_i.to_s,
|
|
37
|
+
url: "https://#{@configuration.storage_zone_name}.b-cdn.net"
|
|
38
|
+
)
|
|
39
|
+
rescue => error
|
|
40
|
+
Result.new(provider: "Bunny", error: error.message)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
STORAGE_HOSTS = {
|
|
46
|
+
"de" => "storage.bunnycdn.com",
|
|
47
|
+
"la" => "la.storage.bunnycdn.com",
|
|
48
|
+
"ny" => "la.storage.bunnycdn.com",
|
|
49
|
+
"sg" => "sg.storage.bunnycdn.com",
|
|
50
|
+
"syd" => "syd.storage.bunnycdn.com"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
def upload(file)
|
|
54
|
+
relative_path = file.delete_prefix("#{@path}/")
|
|
55
|
+
uri = URI("https://#{storage_host}/#{@configuration.storage_zone_name}/#{relative_path}")
|
|
56
|
+
|
|
57
|
+
request = Net::HTTP::Put.new(uri)
|
|
58
|
+
request["AccessKey"] = @configuration.storage_zone_password
|
|
59
|
+
|
|
60
|
+
File.open(file, "rb") do |file|
|
|
61
|
+
request.body = file.read
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
65
|
+
http.request(request)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
return if response.code.to_i == 201
|
|
69
|
+
|
|
70
|
+
raise DeploymentError, "Bunny upload error: #{response.code} #{response.body}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def storage_host
|
|
74
|
+
STORAGE_HOSTS[@configuration.region] || STORAGE_HOSTS["de"]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "beam_up/providers/s3_compatible"
|
|
4
|
+
|
|
5
|
+
module BeamUp
|
|
6
|
+
module Providers
|
|
7
|
+
class DigitalOceanSpaces < S3Compatible
|
|
8
|
+
class Config
|
|
9
|
+
def self.config_keys = %w[access_key secret_key region space_name]
|
|
10
|
+
|
|
11
|
+
attr_accessor :access_key, :secret_key, :region, :space_name
|
|
12
|
+
|
|
13
|
+
def with(options)
|
|
14
|
+
self.access_key = options[:access_key]
|
|
15
|
+
self.secret_key = options[:secret_key]
|
|
16
|
+
self.region = options[:region]
|
|
17
|
+
self.space_name = options[:space_name]
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def validate!
|
|
22
|
+
raise ConfigurationError, "Access key must be set" unless access_key
|
|
23
|
+
raise ConfigurationError, "Secret key must be set" unless secret_key
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def bucket_name
|
|
30
|
+
return @configuration.space_name if @configuration.space_name
|
|
31
|
+
return @created_bucket_name if @created_bucket_name
|
|
32
|
+
|
|
33
|
+
name = "#{File.basename(Dir.pwd)}-#{SecureRandom.hex(4)}"
|
|
34
|
+
s3_client.create_bucket(bucket: name)
|
|
35
|
+
|
|
36
|
+
@created_bucket_name = name
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def endpoint = "https://#{@configuration.region}.digitaloceanspaces.com"
|
|
40
|
+
|
|
41
|
+
def public_url = "https://#{bucket_name}.#{@configuration.region}.cdn.digitaloceanspaces.com"
|
|
42
|
+
|
|
43
|
+
def provider_name = "DigitalOcean Spaces"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|