peas-cli 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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