ezpaas-cli 0.1.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 +73 -0
- data/bin/console +14 -0
- data/bin/ezpaas +12 -0
- data/bin/setup +8 -0
- data/lib/ezpaas/cli.rb +19 -0
- data/lib/ezpaas/cli/commands/apps.rb +102 -0
- data/lib/ezpaas/cli/commands/deployments.rb +131 -0
- data/lib/ezpaas/cli/commands/server_commands.rb +19 -0
- data/lib/ezpaas/cli/version.rb +5 -0
- data/lib/ezpaas/http/http_client.rb +24 -0
- data/lib/ezpaas/http/rest_client.rb +46 -0
- data/lib/ezpaas/http/sse/event_source.rb +94 -0
- data/lib/ezpaas/http/sse_client.rb +70 -0
- data/lib/ezpaas/rest_client.rb +58 -0
- metadata +172 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 10fb16fdc72f68849bb66c48cdf1d4525fad2da4
|
4
|
+
data.tar.gz: c84bb12b4670417135b8502f6a6e91bcda9f81fa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 29d4ba4348233ba5a1e6094960bb3f9b38e488bdd8a44be3b9e117af07cece9593f70b06b1a54d3d55c1710398f7595f2c00d96df12e81cdf571dbfc5bab2217
|
7
|
+
data.tar.gz: d45f763a0d9ec8ce649f4ab7db8c86f9a6d94c2db7569f389b145d9d8a7a4dfefcd86f462cb73eed67eb674de7dc0c57a618fb1173e09ffbea9d36eb729da4f9
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Nick Lee
|
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,73 @@
|
|
1
|
+
# EzPaaS CLI
|
2
|
+
### A miniature Heroku clone for easy in-house deployments, powered by Docker.
|
3
|
+
|
4
|
+
## What Is It?
|
5
|
+
|
6
|
+
At [Tendigi](http://www.tendigi.com), we build applications for a variety of clients, often simultaneously, and those applications usually require server-side infrastructure. We also build [random things internally](https://blog.tendigi.com/people-who-are-really-serious-about-software-should-make-their-own-hardware-6983007e7427) from time to time, and these often depend on services that have to live *somewhere*.
|
7
|
+
|
8
|
+
For production deployments, we love [Heroku](https://heroku.com) (when it makes financial sense) as well as systems like [Deis](https://deis.com/) which can be deployed on AWS / DigitalOcean / etc.
|
9
|
+
|
10
|
+
We longed for a simple, on-site [PaaS](https://en.wikipedia.org/wiki/Platform_as_a_service) solution that we could hack on as our needs evolved. [Dokku](https://github.com/dokku/dokku) is a great project, but we ran into some issues with it (problems updating to newer versions, discrepancies in application behavior compared to our other Deis deployments, a little annoying to work on because it's a collection of shell scripts, etc). As a result, we built EzPaaS: a mini Heroku clone, built in Ruby, powered by Deis images running on Docker.
|
11
|
+
|
12
|
+
## Prerequsites
|
13
|
+
|
14
|
+
#### EzPaaS Server
|
15
|
+
|
16
|
+
**Important!** To use this CLI utility, you need to have [EzPaaS Server](https://github.com/TENDIGI/ezpaas-server) deployed somewhere. This can be on your local machine (the CLI will connect to `localhost` on port 3000 by default) or a remote server by passing the `--server` option.
|
17
|
+
|
18
|
+
#### Ruby
|
19
|
+
|
20
|
+
EzPaaS also requires [Ruby 2.2 or newer](https://www.ruby-lang.org/en/downloads/). It may work with older versions, but they have not been tested.
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
Install the gem. The easiest way is to install it for all users with `sudo`:
|
25
|
+
|
26
|
+
`$ sudo gem install ezpaas-cli`
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
### Create an app
|
31
|
+
|
32
|
+
`$ ezpaas apps create`
|
33
|
+
|
34
|
+

|
35
|
+
|
36
|
+
### List all apps on the server
|
37
|
+
|
38
|
+
`$ ezpaas apps list`
|
39
|
+
|
40
|
+

|
41
|
+
|
42
|
+
### Deploy an app
|
43
|
+
|
44
|
+
`$ ezpaas deployments push --app=<app name>`
|
45
|
+
|
46
|
+

|
47
|
+
|
48
|
+
### Scale an app
|
49
|
+
|
50
|
+
`$ ezpaas deployments scale [<process=count>...] --app=<app name>`
|
51
|
+
|
52
|
+

|
53
|
+
|
54
|
+
### Take an app down
|
55
|
+
|
56
|
+
`$ ezpaas deployments destroy --app=<app name>`
|
57
|
+
|
58
|
+

|
59
|
+
|
60
|
+
### Delete an app
|
61
|
+
|
62
|
+
`$ ezpaas apps destroy`
|
63
|
+
|
64
|
+

|
65
|
+
|
66
|
+
|
67
|
+
## Contributing
|
68
|
+
|
69
|
+
Bug reports and pull requests are welcome on [GitHub]().
|
70
|
+
|
71
|
+
## License
|
72
|
+
|
73
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "ezpaas/cli"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/ezpaas
ADDED
data/bin/setup
ADDED
data/lib/ezpaas/cli.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'Thor'
|
2
|
+
|
3
|
+
require 'ezpaas/cli/version'
|
4
|
+
require 'ezpaas/cli/commands/apps'
|
5
|
+
require 'ezpaas/cli/commands/deployments'
|
6
|
+
|
7
|
+
module EzPaaS
|
8
|
+
module CLI
|
9
|
+
class Main < Thor
|
10
|
+
|
11
|
+
desc 'apps', 'Manage apps registered with the EzPaaS server'
|
12
|
+
subcommand 'apps', Commands::Apps
|
13
|
+
|
14
|
+
desc 'deployments', 'Manage code deployments for your apps'
|
15
|
+
subcommand 'deployments', Commands::Deployments
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'tty'
|
3
|
+
require 'random-word'
|
4
|
+
require 'uri'
|
5
|
+
require 'ezpaas/cli/commands/server_commands'
|
6
|
+
require 'ezpaas/http/rest_client'
|
7
|
+
|
8
|
+
module EzPaaS
|
9
|
+
module CLI
|
10
|
+
module Commands
|
11
|
+
class Apps < ServerCommands
|
12
|
+
|
13
|
+
desc 'list', 'Lists all apps registered with the EzPaaS server'
|
14
|
+
def list
|
15
|
+
apps = client.apps
|
16
|
+
|
17
|
+
pastel = Pastel.new
|
18
|
+
not_deployed = pastel.red('(not deployed)')
|
19
|
+
|
20
|
+
table = TTY::Table.new(header: ['Name', 'Slug', 'Scale', 'URL']) do |t|
|
21
|
+
apps.each do |app|
|
22
|
+
|
23
|
+
app_name = app['name']
|
24
|
+
|
25
|
+
app_slug = app['slug']
|
26
|
+
|
27
|
+
url_string = app_slug ? URI::join(options[:server], "proxy/#{app_name}/") : not_deployed
|
28
|
+
|
29
|
+
slug_string = app_slug || not_deployed
|
30
|
+
|
31
|
+
if scale = app['scale']
|
32
|
+
scale_string = app['scale'].map{ |k, v| "#{k}=#{v}" }.join('\n')
|
33
|
+
else
|
34
|
+
scale_string = not_deployed
|
35
|
+
end
|
36
|
+
|
37
|
+
t << [app_name, slug_string, scale_string, url_string]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
puts
|
42
|
+
puts table.render(:unicode, multiline: true)
|
43
|
+
end
|
44
|
+
|
45
|
+
desc 'create', 'Creates a new app on the EzPaaS server'
|
46
|
+
option :name, :type => :string, :banner => '<app name>'
|
47
|
+
def create
|
48
|
+
app_name = options[:name]
|
49
|
+
|
50
|
+
unless app_name
|
51
|
+
prompt = TTY::Prompt.new
|
52
|
+
|
53
|
+
default = loop do
|
54
|
+
default = "#{RandomWord.adjs(not_longer_than: 10).next}-#{RandomWord.nouns(not_longer_than: 10).next}"
|
55
|
+
break default unless default.include?('_')
|
56
|
+
end
|
57
|
+
|
58
|
+
app_name = prompt.ask('What would you like to call your new app?', required: true, default: default)
|
59
|
+
end
|
60
|
+
|
61
|
+
pastel = Pastel.new
|
62
|
+
|
63
|
+
app = client.create_app(app_name)['app']
|
64
|
+
app_name = pastel.blue(app['name'])
|
65
|
+
|
66
|
+
puts
|
67
|
+
puts "App #{app_name} created successfully!"
|
68
|
+
end
|
69
|
+
|
70
|
+
desc 'destroy', 'Destroys an app on the EzPaaS server'
|
71
|
+
option :app, :type => :string, :banner => '<app name>'
|
72
|
+
def destroy
|
73
|
+
pastel = Pastel.new
|
74
|
+
|
75
|
+
puts
|
76
|
+
puts '🚨 🚨 🚨 ' + pastel.red('WARNING: Destroying an app is ') + pastel.red.bold.underline('not') + pastel.red(' reversible!') + ' 🚨 🚨 🚨'
|
77
|
+
puts
|
78
|
+
|
79
|
+
app_name = options[:app]
|
80
|
+
|
81
|
+
prompt = TTY::Prompt.new
|
82
|
+
|
83
|
+
unless app_name
|
84
|
+
apps = client.apps
|
85
|
+
app_names = apps.map { |e| e['name'] }
|
86
|
+
app_name = prompt.select('Which app would you like to destroy?', app_names)
|
87
|
+
end
|
88
|
+
|
89
|
+
if prompt.yes?('Are you sure?', default: false)
|
90
|
+
client.destroy_app(app_name)
|
91
|
+
puts
|
92
|
+
puts "App #{app_name} destroyed successfully!"
|
93
|
+
else
|
94
|
+
puts
|
95
|
+
puts pastel.blue('Phew!') + ' App deletion aborted.'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'git'
|
3
|
+
require 'tmpdir'
|
4
|
+
require 'tty'
|
5
|
+
require 'uri'
|
6
|
+
require 'ezpaas/cli/commands/server_commands'
|
7
|
+
require 'ezpaas/http/sse_client'
|
8
|
+
|
9
|
+
module EzPaaS
|
10
|
+
module CLI
|
11
|
+
module Commands
|
12
|
+
class Deployments < ServerCommands
|
13
|
+
|
14
|
+
desc 'push', 'Pushes the current git repository'
|
15
|
+
option :app, :type => :string, :required => true
|
16
|
+
option :dir, :type => :string, :default => Dir.pwd
|
17
|
+
option :branch, :type => :string, :default => 'master'
|
18
|
+
def push
|
19
|
+
pastel = Pastel.new
|
20
|
+
|
21
|
+
app = options[:app]
|
22
|
+
dir = options[:dir]
|
23
|
+
branch = options[:branch]
|
24
|
+
|
25
|
+
puts 'Opening git repository at ' + pastel.blue(dir)
|
26
|
+
git = Git.open(dir)
|
27
|
+
branch = git.branches[branch]
|
28
|
+
|
29
|
+
begin
|
30
|
+
path = Dir::Tmpname.create('ezpaas') do |file|
|
31
|
+
puts 'Archiving ' + pastel.blue(branch) + ' branch'
|
32
|
+
branch.archive(file, {format: 'tar'})
|
33
|
+
end
|
34
|
+
|
35
|
+
url_str = URI::join(options[:server], "proxy/#{app}/").to_s
|
36
|
+
success_msg = pastel.green('Application deployment completed')
|
37
|
+
success_msg += "\n" + 'Access your application at ' + pastel.blue(url_str)
|
38
|
+
|
39
|
+
server_comm_wrap(success_msg) do
|
40
|
+
sse_client.deploy(app, path) do |message|
|
41
|
+
puts message
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
ensure
|
46
|
+
File.delete(path)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'destroy', 'Scales all processes of an EzPaas app to zero'
|
51
|
+
option :app, :type => :string, :required => true
|
52
|
+
def destroy
|
53
|
+
pastel = Pastel.new
|
54
|
+
prompt = TTY::Prompt.new
|
55
|
+
|
56
|
+
app = options[:app]
|
57
|
+
|
58
|
+
puts
|
59
|
+
puts '🚨 🚨 🚨 ' + pastel.red("WARNING: You're about to take this app down. People won't be able to access it until you scale it up again.") + ' 🚨 🚨 🚨'
|
60
|
+
puts
|
61
|
+
|
62
|
+
if prompt.yes?('Are you sure?', default: false)
|
63
|
+
success_msg = pastel.green('Application scaling completed')
|
64
|
+
server_comm_wrap(success_msg) do
|
65
|
+
sse_client.destroy(app) do |message|
|
66
|
+
puts message
|
67
|
+
end
|
68
|
+
end
|
69
|
+
else
|
70
|
+
puts
|
71
|
+
puts pastel.blue('Phew!') + ' App scaling aborted.'
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
desc 'scale [<process=count>...]', 'Scales the processes of an EzPaas app'
|
78
|
+
option :app, :type => :string, :required => true
|
79
|
+
def scale(*scales)
|
80
|
+
pastel = Pastel.new
|
81
|
+
|
82
|
+
app = options[:app]
|
83
|
+
|
84
|
+
if scales.empty?
|
85
|
+
raise 'You must provide scaling arguments'
|
86
|
+
end
|
87
|
+
|
88
|
+
new_scale = {}
|
89
|
+
|
90
|
+
scales.each do |s|
|
91
|
+
components = s.split '='
|
92
|
+
raise 'Invalid scale format' unless components.count == 2 and components.first.is_a? String and (components.last.to_i.to_s == components.last) and components.last.to_i >= 0
|
93
|
+
new_scale[components.first] = components.last.to_i
|
94
|
+
end
|
95
|
+
|
96
|
+
puts
|
97
|
+
|
98
|
+
success_msg = pastel.green('Application scaling completed')
|
99
|
+
|
100
|
+
server_comm_wrap(success_msg) do
|
101
|
+
sse_client.scale(app, new_scale) do |message|
|
102
|
+
puts message
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
no_commands do
|
110
|
+
def sse_client
|
111
|
+
HTTP::SSEClient.new(options[:server])
|
112
|
+
end
|
113
|
+
|
114
|
+
def server_comm_wrap(end_msg)
|
115
|
+
screen = TTY::Screen.new
|
116
|
+
msg = 'Opening connection to slug compilation container'
|
117
|
+
puts msg
|
118
|
+
puts '─' * [msg.length, screen.width].min
|
119
|
+
puts
|
120
|
+
yield
|
121
|
+
puts
|
122
|
+
puts '─' * [end_msg.length, screen.width].min
|
123
|
+
puts end_msg
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module EzPaaS
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
class ServerCommands < Thor
|
7
|
+
|
8
|
+
class_option :server, :type => :string, :banner => '<server url>', :default => 'http://127.0.0.1:3000'
|
9
|
+
|
10
|
+
no_commands do
|
11
|
+
def client
|
12
|
+
HTTP::RESTClient.new(options[:server])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module EzPaaS
|
4
|
+
module HTTP
|
5
|
+
class HTTPClient
|
6
|
+
|
7
|
+
class HTTPError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :url
|
11
|
+
|
12
|
+
def initialize(url)
|
13
|
+
@url = url
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def url_for(path)
|
19
|
+
URI::join(url, path).to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'ezpaas/http/http_client'
|
3
|
+
|
4
|
+
module EzPaaS
|
5
|
+
module HTTP
|
6
|
+
class RESTClient < HTTPClient
|
7
|
+
|
8
|
+
include HTTParty
|
9
|
+
|
10
|
+
# Apps
|
11
|
+
|
12
|
+
def apps
|
13
|
+
(handle self.class.get(url_for('/apps')))['apps']
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_app(name)
|
17
|
+
options = {
|
18
|
+
body: {
|
19
|
+
name: name
|
20
|
+
}
|
21
|
+
}
|
22
|
+
handle self.class.post(url_for('/apps'), options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def destroy_app(name)
|
26
|
+
options = {
|
27
|
+
query: {
|
28
|
+
name: name
|
29
|
+
}
|
30
|
+
}
|
31
|
+
handle self.class.delete(url_for('/apps'), options)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def handle(response)
|
37
|
+
if response.code >= 400
|
38
|
+
raise HTTPError, (response['error'] || 'An unknown error occurred.')
|
39
|
+
else
|
40
|
+
response
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module EzPaaS
|
4
|
+
module HTTP
|
5
|
+
module SSE
|
6
|
+
# An EventSource instance is used to parse a stream in the format given by
|
7
|
+
# the Server Sent Events standard: http://www.whatwg.org/specs/web-apps/current-work/#server-sent-events
|
8
|
+
class EventSource
|
9
|
+
class Event < Struct.new(:data, :name, :id);
|
10
|
+
end
|
11
|
+
|
12
|
+
# @!attribute retry [r]
|
13
|
+
# @return [Fixnum] the last retry field received, or nil if no retry fields
|
14
|
+
# have been received.
|
15
|
+
attr_reader :retry
|
16
|
+
|
17
|
+
# @!attribute last_event_id [r]
|
18
|
+
# @return [String] the id of the last fully received event, or nil if no
|
19
|
+
# events have been received containing an id field.
|
20
|
+
attr_reader :last_event_id
|
21
|
+
|
22
|
+
# @!attribute json [rw]
|
23
|
+
# @return [Boolean] (true) Whether to parse event's data field as JSON.
|
24
|
+
attr_accessor :json
|
25
|
+
alias json? json
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@json = true
|
29
|
+
@on = {}
|
30
|
+
@all = []
|
31
|
+
@buffer = ""
|
32
|
+
end
|
33
|
+
|
34
|
+
# Feed a chunk of data to the EventSource and dispatch any fully receieved
|
35
|
+
# events.
|
36
|
+
# @param [String] chunk the data to parse
|
37
|
+
def feed chunk
|
38
|
+
@buffer << chunk
|
39
|
+
|
40
|
+
while i = @buffer.index("\n\n")
|
41
|
+
process_event @buffer.slice!(0..i)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Adds a listener for :name:
|
46
|
+
def on name, &block
|
47
|
+
(@on[name.to_sym] ||= []) << block
|
48
|
+
end
|
49
|
+
|
50
|
+
# Listens to all messages
|
51
|
+
def message &block
|
52
|
+
@all << block
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
def process_event s
|
57
|
+
data, id, name = [], nil, nil
|
58
|
+
s.lines.map(&:chomp).each do |line|
|
59
|
+
field, value = case line
|
60
|
+
when /^:/ then
|
61
|
+
next # comment, do nothing
|
62
|
+
when /^(.*?):(.*)$/ then
|
63
|
+
[$1, $2]
|
64
|
+
else
|
65
|
+
[line, ''] # this is what the spec says, I swear!
|
66
|
+
end
|
67
|
+
# spec allows one optional space after the colon
|
68
|
+
value = value[1..-1] if value.start_with? ' '
|
69
|
+
case field
|
70
|
+
when 'data' then
|
71
|
+
data << value
|
72
|
+
when 'id' then
|
73
|
+
id = value
|
74
|
+
when 'event' then
|
75
|
+
name = value.to_sym
|
76
|
+
when 'retry' then
|
77
|
+
@retry = value.to_i
|
78
|
+
end
|
79
|
+
end
|
80
|
+
@last_event_id = id
|
81
|
+
dispatch_event data.join("\n"), id, name
|
82
|
+
end
|
83
|
+
|
84
|
+
def dispatch_event data, id, name
|
85
|
+
data = JSON.parse(data) if json?
|
86
|
+
name = (name || :message).to_sym
|
87
|
+
event = Event.new(data, name, id)
|
88
|
+
((@on[name] || []) + @all).each { |p| p.call event }
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'excon'
|
2
|
+
require 'json'
|
3
|
+
require 'ezpaas/http/http_client'
|
4
|
+
require 'ezpaas/http/sse/event_source'
|
5
|
+
|
6
|
+
module EzPaaS
|
7
|
+
module HTTP
|
8
|
+
class SSEClient < HTTPClient
|
9
|
+
|
10
|
+
# Deployments
|
11
|
+
|
12
|
+
def deploy(app, file_name)
|
13
|
+
begin
|
14
|
+
file = File.open(file_name, 'rb')
|
15
|
+
|
16
|
+
path = "/deployments?app=#{app}"
|
17
|
+
|
18
|
+
options = {
|
19
|
+
body: file,
|
20
|
+
headers: {'Content-Type': 'application/tar'}
|
21
|
+
}
|
22
|
+
|
23
|
+
streaming_request(:post, path, options) do |message|
|
24
|
+
yield message.data if block_given?
|
25
|
+
end
|
26
|
+
ensure
|
27
|
+
file.close
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def scale(app, scale)
|
32
|
+
scale_qs = scale.map { |k, v| "scale[#{k}]=#{v}" }.join('&')
|
33
|
+
path = "/deployments?app=#{app}&" + scale_qs
|
34
|
+
streaming_request(:patch, path) do |message|
|
35
|
+
yield message.data if block_given?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def destroy(app)
|
40
|
+
path = "/deployments?app=#{app}"
|
41
|
+
streaming_request(:delete, path) do |message|
|
42
|
+
yield message.data if block_given?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def streaming_request(type, path, options = {})
|
49
|
+
url = url_for(path)
|
50
|
+
|
51
|
+
source = SSE::EventSource.new
|
52
|
+
|
53
|
+
source.json = false
|
54
|
+
|
55
|
+
streamer = lambda do |chunk, remaining_bytes, total_bytes|
|
56
|
+
source.feed chunk
|
57
|
+
end
|
58
|
+
|
59
|
+
source.on :message do |event|
|
60
|
+
yield event if block_given?
|
61
|
+
end
|
62
|
+
|
63
|
+
merged_options = options.merge({ :response_block => streamer })
|
64
|
+
|
65
|
+
Excon.send type, url, merged_options
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module EzPaaS
|
5
|
+
class RESTClient
|
6
|
+
|
7
|
+
class RestError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
include HTTParty
|
11
|
+
|
12
|
+
attr_reader :url
|
13
|
+
|
14
|
+
def initialize(url)
|
15
|
+
@url = url
|
16
|
+
end
|
17
|
+
|
18
|
+
# Apps
|
19
|
+
|
20
|
+
def apps
|
21
|
+
(handle self.class.get(url_for('/apps')))['apps']
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_app(name)
|
25
|
+
options = {
|
26
|
+
body: {
|
27
|
+
name: name
|
28
|
+
}
|
29
|
+
}
|
30
|
+
handle self.class.post(url_for('/apps'), options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def destroy_app(name)
|
34
|
+
options = {
|
35
|
+
query: {
|
36
|
+
name: name
|
37
|
+
}
|
38
|
+
}
|
39
|
+
handle self.class.delete(url_for('/apps'), options)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def url_for(path)
|
45
|
+
URI::join(url, path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def handle(response)
|
49
|
+
if response.code >= 400
|
50
|
+
raise ClientError, (response['error'] || 'An unknown error occurred.')
|
51
|
+
else
|
52
|
+
response
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
metadata
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ezpaas-cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nick Lee
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.19.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.19.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: tty
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.7.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.7.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: httparty
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.15.6
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.15.6
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: random-word
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.0.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.0.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: git
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.3.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.3.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: excon
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.58.0
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.58.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.15'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.15'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '10.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '10.0'
|
125
|
+
description:
|
126
|
+
email:
|
127
|
+
- nick@tendigi.com
|
128
|
+
executables:
|
129
|
+
- ezpaas
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- LICENSE.txt
|
134
|
+
- README.md
|
135
|
+
- bin/console
|
136
|
+
- bin/ezpaas
|
137
|
+
- bin/setup
|
138
|
+
- lib/ezpaas/cli.rb
|
139
|
+
- lib/ezpaas/cli/commands/apps.rb
|
140
|
+
- lib/ezpaas/cli/commands/deployments.rb
|
141
|
+
- lib/ezpaas/cli/commands/server_commands.rb
|
142
|
+
- lib/ezpaas/cli/version.rb
|
143
|
+
- lib/ezpaas/http/http_client.rb
|
144
|
+
- lib/ezpaas/http/rest_client.rb
|
145
|
+
- lib/ezpaas/http/sse/event_source.rb
|
146
|
+
- lib/ezpaas/http/sse_client.rb
|
147
|
+
- lib/ezpaas/rest_client.rb
|
148
|
+
homepage: https://github.com/TENDIGI/ezpaas-cli
|
149
|
+
licenses:
|
150
|
+
- MIT
|
151
|
+
metadata: {}
|
152
|
+
post_install_message:
|
153
|
+
rdoc_options: []
|
154
|
+
require_paths:
|
155
|
+
- lib
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
requirements: []
|
167
|
+
rubyforge_project:
|
168
|
+
rubygems_version: 2.4.8
|
169
|
+
signing_key:
|
170
|
+
specification_version: 4
|
171
|
+
summary: A miniature Heroku clone for easy in-house deployments, powered by Docker
|
172
|
+
test_files: []
|