peas-cli 0.6.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ff6bd17390d126df42251a27d4b7cbf3ecee90e9
4
- data.tar.gz: d57aad2e837e5aa3eeca287cd631d4cb23c0600c
3
+ metadata.gz: 148aa934a08e354cd9f2fec7d0ac59a9cf4501f2
4
+ data.tar.gz: fa6cd8baf861cf1e21df8532c58f8659626b0628
5
5
  SHA512:
6
- metadata.gz: 2e13f14149f6ec04c6775781978b705b84b3492b5ba73551c8a2cba4e7a02652a98b012438a3f3bc39fd48682d2e0697d930ba5ea718266e2f719f46dcffdd05
7
- data.tar.gz: b6b6557852a4cee0b445071fec9f79f220848123324bded6fe121c112bd2a25b47cfd7baa4c059ff5729b71757805046cb2c5eaa97fd76d6374d0f524a9fbd50
6
+ metadata.gz: fe827c22e2027ab7ca62bffc09f0f803847c2051b38758230999811b76604c9e9ab7f77fd6324225935f5917b18d395e2f465f9a44ea35f4873218c35454034e
7
+ data.tar.gz: 9089daead2d77e5e4d8c9b29571d2d16775231f4dc85055e3cf62baebf21abb7bdc432b0c1a90798a09cacdc67b4ec291353a04a0ea95076664a58f0578b2e12
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.0
1
+ 0.7.0
data/lib/peas/api.rb CHANGED
@@ -4,7 +4,7 @@ require 'openssl'
4
4
 
5
5
  class API
6
6
  include HTTParty
7
- # TODO: Don't want to genuine SSL cert errors, say if there's a CA root cert
7
+ # TODO: Don't want to ignore genuine SSL cert errors, say if there's a CA root cert
8
8
  default_options.update(verify: false) # Ignore self-signed SSL error
9
9
 
10
10
  LONG_POLL_TIMEOUT = 10 * 60
@@ -16,8 +16,20 @@ class API
16
16
  end
17
17
 
18
18
  # Generic wrapper to the Peas API
19
- def request(verb, method, params = nil)
20
- response = self.class.send(verb, "#{method}", query: params).body
19
+ # `verb` HTTP verb
20
+ # `method` API method, eg; /app/create
21
+ # `query` Query params
22
+ # `auth` Whether to authenticate against the API. Eg; /auth/request doesn't need auth
23
+ # `print` Whether to output or return the response
24
+ def request(verb, method, query = {}, auth = true, print = true)
25
+ options = { query: query }
26
+ options[:headers] = { 'x-api-key' => api_key } if auth
27
+ request = [
28
+ verb.to_s.downcase,
29
+ "#{method}",
30
+ options
31
+ ]
32
+ response = self.class.send(request.shift, *request).body
21
33
  json = response ? JSON.parse(response) : {}
22
34
  # If there was an HTTP-level error
23
35
  raise json['error'].color(:red) if json.key? 'error'
@@ -27,15 +39,42 @@ class API
27
39
  API.stream_job json['job']
28
40
  else
29
41
  check_versions(json)
30
- if block_given?
31
- yield json['message']
32
- else
33
- puts json['message']
34
- end
42
+ puts json['message'] if print
35
43
  json
36
44
  end
37
45
  end
38
46
 
47
+ # Get the API key from local cache, or request a new one
48
+ def api_key
49
+ # First check local storage
50
+ key = Peas.config['api_key']
51
+ return key if key
52
+ # Other wise request a new one
53
+ key_path = "#{ENV['HOME']}/.ssh/id_rsa"
54
+ unless File.exist? key_path
55
+ exit_now! 'Please add an SSH key'
56
+ end
57
+ username = ENV['USER'] # TODO: Ensure cross platform
58
+ params = {
59
+ username: username,
60
+ public_key: File.read("#{key_path}.pub")
61
+ }
62
+ response = request('POST', '/auth/request', params, auth: false, print: false)
63
+ doc = response['message']['sign']
64
+ digest = OpenSSL::Digest::SHA256.new
65
+ keypair = OpenSSL::PKey::RSA.new(File.read(key_path))
66
+ signature = keypair.sign(digest, doc)
67
+ encoded = Base64.urlsafe_encode64(signature)
68
+ params = {
69
+ username: username,
70
+ signed: encoded
71
+ }
72
+ response = request('POST', '/auth/verify', params, auth: false, print: false)
73
+ key = response['message']['api_key']
74
+ Peas.update_config api_key: key
75
+ key
76
+ end
77
+
39
78
  # Check CLI client is up to date.
40
79
  def check_versions(json)
41
80
  # Only check major and minor versions
@@ -58,6 +97,11 @@ class API
58
97
  ssl = OpenSSL::SSL::SSLSocket.new socket
59
98
  ssl.sync_close = true
60
99
  ssl.connect
100
+ ssl.puts API.new.api_key
101
+ unless ssl.gets.strip == 'AUTHORISED'
102
+ ssl.close
103
+ raise 'Unauthoirsed access to Switchboard connection.'
104
+ end
61
105
  ssl
62
106
  end
63
107
 
@@ -1,7 +1,7 @@
1
1
  def format_settings(hash)
2
2
  puts "Available settings"
3
3
  puts ''
4
- hash.each do |type, settings|
4
+ hash['message'].each do |type, settings|
5
5
  puts "#{type.capitalize}:"
6
6
  settings.each do |setting, value|
7
7
  value = '[unset]' if value == ''
@@ -23,13 +23,12 @@ command :admin do |admin|
23
23
  # Update Git config
24
24
  Git.sh "git config peas.domain #{domain}"
25
25
  # Update file
26
- content = Peas.config.merge('domain' => domain).to_json
27
- File.open(Peas.config_file, 'w+') { |f| f.write(content) }
26
+ Peas.update_config domain: domain
28
27
  @api = API.new # Refresh settings from git/file because there's a new domain URI
29
28
  end
30
- @api.request(:put, '/admin/settings', args[0] => args[1]) { |response| format_settings response }
29
+ format_settings @api.request(:put, '/admin/settings', { args[0] => args[1] }, true, false)
31
30
  else
32
- @api.request(:get, '/admin/settings') { |response| format_settings response }
31
+ format_settings @api.request(:get, '/admin/settings', true, false)
33
32
  end
34
33
  end
35
34
  end
@@ -15,11 +15,13 @@ command :create do |c|
15
15
  unless File.exist? public_key_path
16
16
  exit_now! "Couldn't find an SSH public key", 1
17
17
  end
18
+ params = {
19
+ 'muse' => Git.name_from_remote(Git.remote('origin')),
20
+ }
18
21
  response = @api.request(
19
22
  :post,
20
23
  '/app',
21
- muse: Git.name_from_remote(Git.remote('origin')),
22
- public_key: File.open(public_key_path).read
24
+ params
23
25
  )
24
26
  Git.add_remote response['remote_uri']
25
27
  end
@@ -55,7 +57,7 @@ command :scale do |c|
55
57
  @api.request(
56
58
  :put,
57
59
  "/app/#{Git.name_from_remote}/scale",
58
- scaling_hash: scaling_hash.to_json
60
+ 'scaling_hash' => scaling_hash.to_json
59
61
  )
60
62
  end
61
63
  end
@@ -11,7 +11,7 @@ command :config do |c|
11
11
  @api.request(
12
12
  :delete,
13
13
  "/app/#{Git.name_from_remote}/config",
14
- keys: args.to_json
14
+ 'keys' => args.to_json
15
15
  )
16
16
  end
17
17
  end
@@ -32,7 +32,7 @@ command :config do |c|
32
32
  @api.request(
33
33
  :put,
34
34
  "/app/#{Git.name_from_remote}/config",
35
- vars: vars.to_json
35
+ 'vars' => vars.to_json
36
36
  )
37
37
  end
38
38
  end
data/lib/peas/config.rb CHANGED
@@ -14,6 +14,12 @@ module Peas
14
14
  JSON.parse contents
15
15
  end
16
16
 
17
+ # Merge new key/values into the config file
18
+ def self.update_config(hash)
19
+ content = Peas.config.merge(hash).to_json
20
+ File.open(Peas.config_file, 'w+') { |f| f.write(content) }
21
+ end
22
+
17
23
  # Hierarchy of sources for the Peas API domain
18
24
  def self.api_domain
19
25
  git_domain = Git.sh 'git config peas.domain'
@@ -25,7 +31,7 @@ module Peas
25
31
  elsif Peas.config['domain']
26
32
  Peas.config['domain']
27
33
  else
28
- 'vcap.me:4000'
34
+ 'vcap.me:4443'
29
35
  end
30
36
  unless domain[/\Ahttp:\/\//] || domain[/\Ahttps:\/\//]
31
37
  "https://#{domain}"
data/peas-cli.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: peas-cli 0.6.0 ruby lib
5
+ # stub: peas-cli 0.7.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "peas-cli"
9
- s.version = "0.6.0"
9
+ s.version = "0.7.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Tom Buckley-Houston"]
14
- s.date = "2015-01-03"
14
+ s.date = "2015-01-17"
15
15
  s.description = "Peas is an open source Heroku-style PaaS written in Ruby and using Docker"
16
16
  s.email = "tom@tombh.co.uk"
17
17
  s.executables = ["peas"]
data/spec/cli_spec.rb CHANGED
@@ -5,6 +5,7 @@ describe 'Peas CLI' do
5
5
  allow(Git).to receive(:sh).and_return(nil)
6
6
  allow(Git).to receive(:remote).and_return('git@github.com:test-test.git')
7
7
  allow_any_instance_of(API).to receive(:sleep).and_return(nil)
8
+ allow_any_instance_of(API).to receive(:api_key).and_return 'APIKEY'
8
9
  allow(Peas).to receive(:config_file).and_return('/tmp/.peas')
9
10
  File.delete '/tmp/.peas' if File.exist? '/tmp/.peas'
10
11
  end
@@ -19,7 +20,8 @@ describe 'Peas CLI' do
19
20
  describe 'Settings' do
20
21
  it 'should set and use the domain setting' do
21
22
  stub_request(:put, 'https://new-domain.com:4000/admin/settings?peas.domain=new-domain.com:4000')
22
- .to_return(body: response_mock({}))
23
+ .with(headers: { 'X-Api-Key' => 'APIKEY' })
24
+ .to_return(body: response_mock)
23
25
  expect(Git).to receive(:sh).with("git config peas.domain https://new-domain.com:4000")
24
26
  cli %w(admin settings peas.domain new-domain.com:4000)
25
27
  config = JSON.parse File.open('/tmp/.peas').read
@@ -27,7 +29,8 @@ describe 'Peas CLI' do
27
29
  end
28
30
 
29
31
  it 'should set a normal setting' do
30
- stub_request(:put, 'https://vcap.me:4000/admin/settings?mongodb.uri=mongodb://uri')
32
+ stub_request(:put, TEST_DOMAIN + '/admin/settings?mongodb.uri=mongodb://uri')
33
+ .with(headers: { 'X-Api-Key' => 'APIKEY' })
31
34
  .to_return(body: response_mock(
32
35
  defaults: { 'peas.domain' => 'https://boss.com' },
33
36
  services: {
@@ -44,17 +47,15 @@ describe 'Peas CLI' do
44
47
  describe 'App methods' do
45
48
  it 'should list all apps' do
46
49
  stub_request(:get, TEST_DOMAIN + '/app')
50
+ .with(headers: { 'X-Api-Key' => 'APIKEY' })
47
51
  .to_return(body: response_mock(["coolapp"]))
48
52
  output = cli ['apps']
49
53
  expect(output).to eq "coolapp\n"
50
54
  end
51
55
 
52
56
  it 'should create an app and its remote' do
53
- public_key_path = "#{ENV['HOME']}/.ssh/id_rsa.pub"
54
- allow(File).to receive(:open).and_call_original
55
- expect(File).to receive(:exist?).with(public_key_path) { true }
56
- expect(File).to receive(:open).with(public_key_path) { double(read: 'apublickey') }
57
- stub_request(:post, TEST_DOMAIN + '/app?muse=test-test&public_key=apublickey')
57
+ stub_request(:post, TEST_DOMAIN + '/app?muse=test-test')
58
+ .with(headers: { 'X-Api-Key' => 'APIKEY' })
58
59
  .to_return(
59
60
  body: {
60
61
  version: Peas::VERSION,
@@ -71,6 +72,7 @@ describe 'Peas CLI' do
71
72
 
72
73
  it 'should destroy an app' do
73
74
  stub_request(:delete, TEST_DOMAIN + '/app/test-test')
75
+ .with(headers: { 'X-Api-Key' => 'APIKEY' })
74
76
  .to_return(body: response_mock("App 'test' successfully destroyed"))
75
77
  expect(Git).to receive(:remove_remote)
76
78
  output = cli ['destroy']
@@ -79,11 +81,15 @@ describe 'Peas CLI' do
79
81
 
80
82
  it 'should scale an app', :with_socket do
81
83
  expect(@socket).to receive(:puts).with('subscribe.job_progress.123')
84
+ expect(@socket).to receive(:puts).with "APIKEY"
82
85
  stub_request(
83
86
  :put,
84
87
  TEST_DOMAIN + '/app/test-test/scale?scaling_hash=%7B%22web%22:%223%22,%22worker%22:%222%22%7D'
85
- ).to_return(body: '{"job": "123"}')
88
+ )
89
+ .with(headers: { 'X-Api-Key' => 'APIKEY' })
90
+ .to_return(body: '{"job": "123"}')
86
91
  allow(@socket).to receive(:gets).and_return(
92
+ 'AUTHORISED',
87
93
  '{"body":"scaling"}',
88
94
  '{"status":"complete"}'
89
95
  )
@@ -116,6 +122,7 @@ describe 'Peas CLI' do
116
122
  describe 'Config ENV vars' do
117
123
  it 'should set config for an app' do
118
124
  stub_request(:put, TEST_DOMAIN + '/app/test-test/config?vars=%7B%22FOO%22:%22BAR%22%7D')
125
+ .with(headers: { 'X-Api-Key' => 'APIKEY' })
119
126
  .to_return(body: response_mock("{'FOO' => 'BAR'}"))
120
127
  output = cli %w(config set FOO=BAR)
121
128
  expect(output).to eq "{'FOO' => 'BAR'}\n"
@@ -123,6 +130,7 @@ describe 'Peas CLI' do
123
130
 
124
131
  it 'delete config for an app' do
125
132
  stub_request(:delete, TEST_DOMAIN + '/app/test-test/config?keys=%5B%22FOO%22%5D')
133
+ .with(headers: { 'X-Api-Key' => 'APIKEY' })
126
134
  .to_return(body: response_mock(nil))
127
135
  output = cli %w(config rm FOO)
128
136
  expect(output).to eq "\n"
@@ -130,6 +138,7 @@ describe 'Peas CLI' do
130
138
 
131
139
  it 'should list all config for an app' do
132
140
  stub_request(:get, TEST_DOMAIN + '/app/test-test/config')
141
+ .with(headers: { 'X-Api-Key' => 'APIKEY' })
133
142
  .to_return(body: response_mock("{'FOO' => 'BAR'}\n{'MOO' => 'CAR'}"))
134
143
  output = cli %w(config)
135
144
  expect(output).to eq "{'FOO' => 'BAR'}\n{'MOO' => 'CAR'}\n"
@@ -140,7 +149,13 @@ describe 'Peas CLI' do
140
149
  describe 'Logs' do
141
150
  it 'should stream logs', :with_socket do
142
151
  expect(@socket).to receive(:puts).with('stream_logs.test-test')
143
- allow(@socket).to receive(:gets).and_return("Here's ya logs", "MOAR", false)
152
+ expect(@socket).to receive(:puts).with "APIKEY"
153
+ allow(@socket).to receive(:gets).and_return(
154
+ "AUTHORISED",
155
+ "Here's ya logs",
156
+ "MOAR",
157
+ false
158
+ )
144
159
  output = cli %w(logs)
145
160
  expect(output).to eq "Here's ya logs\nMOAR\n"
146
161
  end
@@ -148,7 +163,14 @@ describe 'Peas CLI' do
148
163
 
149
164
  it 'should retrieve and output a long-running command', :with_socket do
150
165
  expect(@socket).to receive(:puts).with('subscribe.job_progress.123')
151
- allow(@socket).to receive(:gets).and_return("doing", "something", "done", false)
166
+ expect(@socket).to receive(:puts).with "APIKEY"
167
+ allow(@socket).to receive(:gets).and_return(
168
+ "AUTHORISED",
169
+ "doing",
170
+ "something",
171
+ "done",
172
+ false
173
+ )
152
174
  expect(API).to receive(:puts).with "doing"
153
175
  expect(API).to receive(:puts).with "something"
154
176
  expect(API).to receive(:puts).with "done"
@@ -157,6 +179,7 @@ describe 'Peas CLI' do
157
179
 
158
180
  it 'should show a warning when there is a version mismatch' do
159
181
  stub_request(:get, TEST_DOMAIN + '/app/test-test/config')
182
+ .with(headers: { 'X-Api-Key' => 'APIKEY' })
160
183
  .to_return(body: '{"version": "100000.1000000.100000"}')
161
184
  output = cli %w(config)
162
185
  expect(output).to include 'Your version of the CLI client is out of date'
data/spec/spec_helper.rb CHANGED
@@ -9,7 +9,7 @@ require 'lib/peas'
9
9
  ENV['GLI_ENV'] = 'test'
10
10
  ROOT = File.join(File.expand_path(File.dirname(__FILE__)), '..')
11
11
  $LOAD_PATH.unshift(File.join(ROOT, 'lib'))
12
- TEST_DOMAIN = 'https://vcap.me:4000'
12
+ TEST_DOMAIN = 'https://vcap.me:4443'
13
13
  SWITCHBOARD_TEST_PORT = 79345
14
14
  SSL_KEY_PATH = "#{ROOT}/../contrib/ssl-keys/server.key"
15
15
  SSL_KEY = OpenSSL::PKey::RSA.new File.read(SSL_KEY_PATH)
@@ -34,6 +34,7 @@ RSpec.configure do |config|
34
34
  end
35
35
 
36
36
  config.before(:each, :with_echo_server) do
37
+ allow_any_instance_of(API).to receive(:api_key).and_return('APIKEY')
37
38
  tcp_server = TCPServer.new 'vcap.me', SWITCHBOARD_TEST_PORT
38
39
  context = OpenSSL::SSL::SSLContext.new
39
40
  context.key = SSL_KEY
@@ -43,6 +44,12 @@ RSpec.configure do |config|
43
44
  @connection = @server.accept
44
45
  begin
45
46
  Timeout.timeout(2) do
47
+ auth = @connection.gets.strip
48
+ if auth == 'APIKEY'
49
+ @connection.puts 'AUTHORISED'
50
+ else
51
+ @connection.puts 'UNAUTHORISED'
52
+ end
46
53
  while (line = @connection.gets)
47
54
  @connection.write line
48
55
  @connection.close if line.strip == 'FINAL COMMAND'
@@ -73,7 +80,7 @@ end
73
80
 
74
81
  # Form a response as the API would. Useful as you only need to provide a string without any JSON
75
82
  # formatting
76
- def response_mock(response, key = :message)
83
+ def response_mock(response = {}, key = :message)
77
84
  {
78
85
  'version' => Peas::VERSION,
79
86
  key => response
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: peas-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Buckley-Houston
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-03 00:00:00.000000000 Z
11
+ date: 2015-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gli