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.
@@ -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
@@ -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.
@@ -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
+ ![](./assets/create-app.gif)
35
+
36
+ ### List all apps on the server
37
+
38
+ `$ ezpaas apps list`
39
+
40
+ ![](./assets/list-apps.gif)
41
+
42
+ ### Deploy an app
43
+
44
+ `$ ezpaas deployments push --app=<app name>`
45
+
46
+ ![](./assets/deploy-app.gif)
47
+
48
+ ### Scale an app
49
+
50
+ `$ ezpaas deployments scale [<process=count>...] --app=<app name>`
51
+
52
+ ![](./assets/scale-app.gif)
53
+
54
+ ### Take an app down
55
+
56
+ `$ ezpaas deployments destroy --app=<app name>`
57
+
58
+ ![](./assets/take-app-down.gif)
59
+
60
+ ### Delete an app
61
+
62
+ `$ ezpaas apps destroy`
63
+
64
+ ![](./assets/destroy-app.gif)
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).
@@ -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__)
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ezpaas/cli'
4
+ require 'tty'
5
+
6
+ begin
7
+ EzPaaS::CLI::Main.start(ARGV)
8
+ rescue Exception => e
9
+ pastel = Pastel.new
10
+ puts
11
+ puts pastel.red.bold('Error:') + ' ' + e.message
12
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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,5 @@
1
+ module EzPaaS
2
+ module CLI
3
+ VERSION = "0.1.0"
4
+ end
5
+ 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: []