azuki 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +71 -0
- data/bin/azuki +17 -0
- data/data/cacert.pem +3988 -0
- data/lib/azuki.rb +17 -0
- data/lib/azuki/auth.rb +339 -0
- data/lib/azuki/cli.rb +38 -0
- data/lib/azuki/client.rb +764 -0
- data/lib/azuki/client/azuki_postgresql.rb +141 -0
- data/lib/azuki/client/cisaurus.rb +26 -0
- data/lib/azuki/client/pgbackups.rb +113 -0
- data/lib/azuki/client/rendezvous.rb +108 -0
- data/lib/azuki/client/ssl_endpoint.rb +25 -0
- data/lib/azuki/command.rb +294 -0
- data/lib/azuki/command/account.rb +23 -0
- data/lib/azuki/command/accounts.rb +34 -0
- data/lib/azuki/command/addons.rb +305 -0
- data/lib/azuki/command/apps.rb +393 -0
- data/lib/azuki/command/auth.rb +86 -0
- data/lib/azuki/command/base.rb +230 -0
- data/lib/azuki/command/certs.rb +209 -0
- data/lib/azuki/command/config.rb +137 -0
- data/lib/azuki/command/db.rb +218 -0
- data/lib/azuki/command/domains.rb +85 -0
- data/lib/azuki/command/drains.rb +46 -0
- data/lib/azuki/command/fork.rb +164 -0
- data/lib/azuki/command/git.rb +64 -0
- data/lib/azuki/command/help.rb +179 -0
- data/lib/azuki/command/keys.rb +115 -0
- data/lib/azuki/command/labs.rb +147 -0
- data/lib/azuki/command/logs.rb +45 -0
- data/lib/azuki/command/maintenance.rb +61 -0
- data/lib/azuki/command/pg.rb +269 -0
- data/lib/azuki/command/pgbackups.rb +329 -0
- data/lib/azuki/command/plugins.rb +110 -0
- data/lib/azuki/command/ps.rb +232 -0
- data/lib/azuki/command/regions.rb +22 -0
- data/lib/azuki/command/releases.rb +124 -0
- data/lib/azuki/command/run.rb +180 -0
- data/lib/azuki/command/sharing.rb +89 -0
- data/lib/azuki/command/ssl.rb +43 -0
- data/lib/azuki/command/stack.rb +62 -0
- data/lib/azuki/command/status.rb +51 -0
- data/lib/azuki/command/update.rb +47 -0
- data/lib/azuki/command/version.rb +23 -0
- data/lib/azuki/deprecated.rb +5 -0
- data/lib/azuki/deprecated/help.rb +38 -0
- data/lib/azuki/distribution.rb +9 -0
- data/lib/azuki/excon.rb +9 -0
- data/lib/azuki/helpers.rb +517 -0
- data/lib/azuki/helpers/azuki_postgresql.rb +165 -0
- data/lib/azuki/helpers/log_displayer.rb +70 -0
- data/lib/azuki/plugin.rb +163 -0
- data/lib/azuki/updater.rb +171 -0
- data/lib/azuki/version.rb +3 -0
- data/lib/vendor/azuki/okjson.rb +598 -0
- data/spec/azuki/auth_spec.rb +256 -0
- data/spec/azuki/client/azuki_postgresql_spec.rb +71 -0
- data/spec/azuki/client/pgbackups_spec.rb +43 -0
- data/spec/azuki/client/rendezvous_spec.rb +62 -0
- data/spec/azuki/client/ssl_endpoint_spec.rb +48 -0
- data/spec/azuki/client_spec.rb +564 -0
- data/spec/azuki/command/addons_spec.rb +601 -0
- data/spec/azuki/command/apps_spec.rb +351 -0
- data/spec/azuki/command/auth_spec.rb +38 -0
- data/spec/azuki/command/base_spec.rb +109 -0
- data/spec/azuki/command/certs_spec.rb +178 -0
- data/spec/azuki/command/config_spec.rb +144 -0
- data/spec/azuki/command/db_spec.rb +110 -0
- data/spec/azuki/command/domains_spec.rb +87 -0
- data/spec/azuki/command/drains_spec.rb +34 -0
- data/spec/azuki/command/fork_spec.rb +56 -0
- data/spec/azuki/command/git_spec.rb +144 -0
- data/spec/azuki/command/help_spec.rb +93 -0
- data/spec/azuki/command/keys_spec.rb +120 -0
- data/spec/azuki/command/labs_spec.rb +100 -0
- data/spec/azuki/command/logs_spec.rb +60 -0
- data/spec/azuki/command/maintenance_spec.rb +51 -0
- data/spec/azuki/command/pg_spec.rb +236 -0
- data/spec/azuki/command/pgbackups_spec.rb +307 -0
- data/spec/azuki/command/plugins_spec.rb +104 -0
- data/spec/azuki/command/ps_spec.rb +195 -0
- data/spec/azuki/command/releases_spec.rb +130 -0
- data/spec/azuki/command/run_spec.rb +83 -0
- data/spec/azuki/command/sharing_spec.rb +59 -0
- data/spec/azuki/command/stack_spec.rb +46 -0
- data/spec/azuki/command/status_spec.rb +48 -0
- data/spec/azuki/command/version_spec.rb +16 -0
- data/spec/azuki/command_spec.rb +211 -0
- data/spec/azuki/helpers/azuki_postgresql_spec.rb +155 -0
- data/spec/azuki/helpers_spec.rb +48 -0
- data/spec/azuki/plugin_spec.rb +172 -0
- data/spec/azuki/updater_spec.rb +44 -0
- data/spec/helper/legacy_help.rb +16 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +224 -0
- data/spec/support/display_message_matcher.rb +49 -0
- data/spec/support/openssl_mock_helper.rb +8 -0
- metadata +211 -0
@@ -0,0 +1,165 @@
|
|
1
|
+
require "azuki/helpers"
|
2
|
+
|
3
|
+
module Azuki::Helpers::AzukiPostgresql
|
4
|
+
|
5
|
+
extend self
|
6
|
+
extend Azuki::Helpers
|
7
|
+
|
8
|
+
class Attachment
|
9
|
+
attr_reader :config_var, :resource_name, :url, :addon, :plan
|
10
|
+
def initialize(raw)
|
11
|
+
@raw = raw
|
12
|
+
@config_var = raw['config_var']
|
13
|
+
@resource_name = raw['resource']['name']
|
14
|
+
@url = raw['resource']['value']
|
15
|
+
@addon, @plan = raw['resource']['type'].split(':')
|
16
|
+
end
|
17
|
+
|
18
|
+
def starter_plan?
|
19
|
+
plan =~ /dev|basic/
|
20
|
+
end
|
21
|
+
|
22
|
+
def display_name
|
23
|
+
config_var + (primary_attachment? ? " (DATABASE_URL)" : '')
|
24
|
+
end
|
25
|
+
|
26
|
+
def primary_attachment!
|
27
|
+
@primary_attachment = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def primary_attachment?
|
31
|
+
@primary_attachment
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def hpg_addon_name
|
36
|
+
if ENV['SHOGUN']
|
37
|
+
"shogun-#{ENV['SHOGUN']}"
|
38
|
+
else
|
39
|
+
ENV['AZUKI_POSTGRESQL_ADDON_NAME'] || 'azuki-postgresql'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def app_config_vars
|
44
|
+
@app_config_vars ||= api.get_config_vars(app).body
|
45
|
+
end
|
46
|
+
|
47
|
+
def app_attachments
|
48
|
+
@app_attachments ||= api.get_attachments(app).body.map { |raw| Attachment.new(raw) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def hpg_databases
|
52
|
+
return @hpg_databases if @hpg_databases
|
53
|
+
pairs = app_attachments.select {|att|
|
54
|
+
att.addon == hpg_addon_name
|
55
|
+
}.map { |att|
|
56
|
+
[att.config_var, att]
|
57
|
+
}
|
58
|
+
@hpg_databases = Hash[ pairs ]
|
59
|
+
|
60
|
+
if find_database_url_real_attachment
|
61
|
+
@hpg_databases['DATABASE_URL'] = find_database_url_real_attachment
|
62
|
+
end
|
63
|
+
|
64
|
+
if app_config_vars['SHARED_DATABASE_URL']
|
65
|
+
@hpg_databases['SHARED_DATABASE'] = Attachment.new({
|
66
|
+
'config_var' => 'SHARED_DATABASE',
|
67
|
+
'resource' => {
|
68
|
+
'name' => 'SHARED_DATABASE',
|
69
|
+
'value' => app_config_vars['SHARED_DATABASE_URL'],
|
70
|
+
'type' => 'shared:database'
|
71
|
+
}
|
72
|
+
})
|
73
|
+
end
|
74
|
+
|
75
|
+
return @hpg_databases
|
76
|
+
end
|
77
|
+
|
78
|
+
def resource_url(resource)
|
79
|
+
api.get_resource(resource).body['value']
|
80
|
+
end
|
81
|
+
|
82
|
+
def forget_config!
|
83
|
+
@hpg_databases = nil
|
84
|
+
@app_config_vars = nil
|
85
|
+
@app_attachments = nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def find_database_url_real_attachment
|
89
|
+
raw_primary_db_url = app_config_vars['DATABASE_URL']
|
90
|
+
return unless raw_primary_db_url
|
91
|
+
|
92
|
+
primary_db_url = raw_primary_db_url.split("?").first
|
93
|
+
return unless primary_db_url && !primary_db_url.empty?
|
94
|
+
|
95
|
+
real_config = app_config_vars.detect {|k,v| k != 'DATABASE_URL' && v == primary_db_url }
|
96
|
+
if real_config
|
97
|
+
real = hpg_databases[real_config.first]
|
98
|
+
real.primary_attachment! if real
|
99
|
+
return real
|
100
|
+
else
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def match_attachments_by_name(name)
|
106
|
+
return [] if name.empty?
|
107
|
+
return [name] if hpg_databases[name]
|
108
|
+
hpg_databases.keys.grep(%r{#{ name }}i)
|
109
|
+
end
|
110
|
+
|
111
|
+
def hpg_resolve(name, default=nil)
|
112
|
+
name = '' if name.nil?
|
113
|
+
name = 'DATABASE_URL' if name == 'DATABASE'
|
114
|
+
|
115
|
+
if hpg_databases.empty?
|
116
|
+
error("Your app has no databases.")
|
117
|
+
end
|
118
|
+
|
119
|
+
found_attachment = nil
|
120
|
+
candidates = match_attachments_by_name(name)
|
121
|
+
if default && name.empty? && app_config_vars[default]
|
122
|
+
found_attachment = hpg_databases[default]
|
123
|
+
elsif candidates.size == 1
|
124
|
+
found_attachment = hpg_databases[candidates.first]
|
125
|
+
end
|
126
|
+
|
127
|
+
if found_attachment.nil?
|
128
|
+
error("Unknown database#{': ' + name unless name.empty?}. Valid options are: #{hpg_databases.keys.sort.join(", ")}")
|
129
|
+
end
|
130
|
+
|
131
|
+
return found_attachment
|
132
|
+
end
|
133
|
+
|
134
|
+
def hpg_translate_fork_and_follow(addon, config)
|
135
|
+
if addon =~ /^#{hpg_addon_name}/
|
136
|
+
%w[fork follow].each do |opt|
|
137
|
+
if val = config[opt]
|
138
|
+
unless val.is_a?(String)
|
139
|
+
error("--#{opt} requires a database argument.")
|
140
|
+
end
|
141
|
+
|
142
|
+
uri = URI.parse(val) rescue nil
|
143
|
+
if uri && uri.scheme
|
144
|
+
argument_url = uri.to_s
|
145
|
+
else
|
146
|
+
attachment = hpg_resolve(val)
|
147
|
+
if attachment.starter_plan?
|
148
|
+
error("#{opt.tr 'f', 'F'} is only available on production databases.")
|
149
|
+
end
|
150
|
+
argument_url = attachment.url
|
151
|
+
end
|
152
|
+
|
153
|
+
config[opt] = argument_url
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def hpg_promote(url)
|
162
|
+
api.put_config_vars(app, "DATABASE_URL" => url)
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "azuki/helpers"
|
2
|
+
|
3
|
+
module Azuki::Helpers
|
4
|
+
class LogDisplayer
|
5
|
+
|
6
|
+
include Azuki::Helpers
|
7
|
+
|
8
|
+
attr_reader :azuki, :app, :opts
|
9
|
+
|
10
|
+
def initialize(azuki, app, opts)
|
11
|
+
@azuki, @app, @opts = azuki, app, opts
|
12
|
+
end
|
13
|
+
|
14
|
+
def display_logs
|
15
|
+
@assigned_colors = {}
|
16
|
+
@line_start = true
|
17
|
+
@token = nil
|
18
|
+
|
19
|
+
azuki.read_logs(app, opts) do |chunk|
|
20
|
+
unless chunk.empty?
|
21
|
+
if STDOUT.isatty && ENV.has_key?("TERM")
|
22
|
+
display(colorize(chunk))
|
23
|
+
else
|
24
|
+
display(chunk)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
rescue Errno::EPIPE
|
29
|
+
rescue Interrupt => interrupt
|
30
|
+
if STDOUT.isatty && ENV.has_key?("TERM")
|
31
|
+
display("\e[0m")
|
32
|
+
end
|
33
|
+
raise(interrupt)
|
34
|
+
end
|
35
|
+
|
36
|
+
COLORS = %w( cyan yellow green magenta red )
|
37
|
+
COLOR_CODES = {
|
38
|
+
"red" => 31,
|
39
|
+
"green" => 32,
|
40
|
+
"yellow" => 33,
|
41
|
+
"magenta" => 35,
|
42
|
+
"cyan" => 36,
|
43
|
+
}
|
44
|
+
|
45
|
+
def colorize(chunk)
|
46
|
+
lines = []
|
47
|
+
chunk.split("\n").map do |line|
|
48
|
+
if parsed_line = parse_log(line)
|
49
|
+
header, identifier, body = parsed_line
|
50
|
+
@assigned_colors[identifier] ||= COLORS[@assigned_colors.size % COLORS.size]
|
51
|
+
lines << [
|
52
|
+
"\e[#{COLOR_CODES[@assigned_colors[identifier]]}m",
|
53
|
+
header,
|
54
|
+
"\e[0m",
|
55
|
+
body,
|
56
|
+
].join("")
|
57
|
+
elsif not line.empty?
|
58
|
+
lines << line
|
59
|
+
end
|
60
|
+
end
|
61
|
+
lines.join("\n")
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse_log(log)
|
65
|
+
return unless parsed = log.match(/^(.*?\[(\w+)([\d\.]+)?\]:)(.*)?$/)
|
66
|
+
[1, 2, 4].map { |i| parsed[i] }
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
data/lib/azuki/plugin.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
# based on the Rails Plugin
|
2
|
+
|
3
|
+
module Azuki
|
4
|
+
class Plugin
|
5
|
+
include Azuki::Helpers
|
6
|
+
extend Azuki::Helpers
|
7
|
+
|
8
|
+
class ErrorUpdatingSymlinkPlugin < StandardError; end
|
9
|
+
|
10
|
+
DEPRECATED_PLUGINS = %w(
|
11
|
+
azuki-cedar
|
12
|
+
azuki-certs
|
13
|
+
azuki-credentials
|
14
|
+
azuki-kill
|
15
|
+
azuki-labs
|
16
|
+
azuki-logging
|
17
|
+
azuki-netrc
|
18
|
+
azuki-pgdumps
|
19
|
+
azuki-postgresql
|
20
|
+
azuki-releases
|
21
|
+
azuki-shared-postgresql
|
22
|
+
azuki-sql-console
|
23
|
+
azuki-status
|
24
|
+
azuki-stop
|
25
|
+
azuki-suggest
|
26
|
+
pgbackups-automate
|
27
|
+
pgcmd
|
28
|
+
azuki-fork
|
29
|
+
)
|
30
|
+
|
31
|
+
attr_reader :name, :uri
|
32
|
+
|
33
|
+
def self.directory
|
34
|
+
File.expand_path("#{home_directory}/.azuki/plugins")
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.list
|
38
|
+
Dir["#{directory}/*"].sort.map do |folder|
|
39
|
+
File.basename(folder)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.load!
|
44
|
+
list.each do |plugin|
|
45
|
+
check_for_deprecation(plugin)
|
46
|
+
next if skip_plugins.include?(plugin)
|
47
|
+
load_plugin(plugin)
|
48
|
+
end
|
49
|
+
# check to see if we are using ddollar/azuki-accounts
|
50
|
+
if list.include?('azuki-accounts') && Azuki::Auth.methods.include?(:fetch_from_account)
|
51
|
+
# setup netrc to match the default, if one exists
|
52
|
+
if default_account = %x{ git config azuki.account }.chomp
|
53
|
+
account = Azuki::Auth.extract_account rescue nil
|
54
|
+
if account && Azuki::Auth.read_credentials != [Azuki::Auth.user, Azuki::Auth.password]
|
55
|
+
Azuki::Auth.credentials = [Azuki::Auth.user, Azuki::Auth.password]
|
56
|
+
Azuki::Auth.write_credentials
|
57
|
+
load("#{File.dirname(__FILE__)}/command/accounts.rb")
|
58
|
+
# kill memoization in case '--account' was passed
|
59
|
+
Azuki::Auth.instance_variable_set(:@account, nil)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.load_plugin(plugin)
|
66
|
+
begin
|
67
|
+
folder = "#{self.directory}/#{plugin}"
|
68
|
+
$: << "#{folder}/lib" if File.directory? "#{folder}/lib"
|
69
|
+
load "#{folder}/init.rb" if File.exists? "#{folder}/init.rb"
|
70
|
+
rescue ScriptError, StandardError => error
|
71
|
+
styled_error(error, "Unable to load plugin #{plugin}.")
|
72
|
+
false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.remove_plugin(plugin)
|
77
|
+
FileUtils.rm_rf("#{self.directory}/#{plugin}")
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.check_for_deprecation(plugin)
|
81
|
+
return unless STDIN.isatty
|
82
|
+
|
83
|
+
if DEPRECATED_PLUGINS.include?(plugin)
|
84
|
+
if confirm "The plugin #{plugin} has been deprecated. Would you like to remove it? (y/N)"
|
85
|
+
remove_plugin(plugin)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.skip_plugins
|
91
|
+
@skip_plugins ||= ENV["SKIP_PLUGINS"].to_s.split(/[ ,]/)
|
92
|
+
end
|
93
|
+
|
94
|
+
def initialize(uri)
|
95
|
+
@uri = uri
|
96
|
+
guess_name(uri)
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_s
|
100
|
+
name
|
101
|
+
end
|
102
|
+
|
103
|
+
def path
|
104
|
+
"#{self.class.directory}/#{name}"
|
105
|
+
end
|
106
|
+
|
107
|
+
def install
|
108
|
+
if File.directory?(path)
|
109
|
+
uninstall
|
110
|
+
end
|
111
|
+
FileUtils.mkdir_p(self.class.directory)
|
112
|
+
Dir.chdir(self.class.directory) do
|
113
|
+
git("clone #{uri}")
|
114
|
+
unless $?.success?
|
115
|
+
FileUtils.rm_rf path
|
116
|
+
return false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
true
|
120
|
+
end
|
121
|
+
|
122
|
+
def uninstall
|
123
|
+
ensure_plugin_exists
|
124
|
+
FileUtils.rm_r(path)
|
125
|
+
end
|
126
|
+
|
127
|
+
def update
|
128
|
+
ensure_plugin_exists
|
129
|
+
if File.symlink?(path)
|
130
|
+
raise Azuki::Plugin::ErrorUpdatingSymlinkPlugin
|
131
|
+
else
|
132
|
+
Dir.chdir(path) do
|
133
|
+
unless git('config --get branch.master.remote').empty?
|
134
|
+
message = git("pull")
|
135
|
+
unless $?.success?
|
136
|
+
error("Unable to update #{name}.\n" + message)
|
137
|
+
end
|
138
|
+
else
|
139
|
+
error(<<-ERROR)
|
140
|
+
#{name} is a legacy plugin installation.
|
141
|
+
Enable updating by reinstalling with `azuki plugins:install`.
|
142
|
+
ERROR
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def ensure_plugin_exists
|
151
|
+
unless File.directory?(path)
|
152
|
+
error("#{name} plugin not found.")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def guess_name(url)
|
157
|
+
@name = File.basename(url)
|
158
|
+
@name = File.basename(File.dirname(url)) if @name.empty?
|
159
|
+
@name.gsub!(/\.git$/, '') if @name =~ /\.git$/
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require "digest"
|
2
|
+
require "fileutils"
|
3
|
+
require "azuki/helpers"
|
4
|
+
|
5
|
+
module Azuki
|
6
|
+
module Updater
|
7
|
+
|
8
|
+
def self.error(message)
|
9
|
+
raise Azuki::Command::CommandFailed.new(message)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.updating_lock_path
|
13
|
+
File.join(Azuki::Helpers.home_directory, ".azuki", "updating")
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.installed_client_path
|
17
|
+
File.expand_path("../../..", __FILE__)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.updated_client_path
|
21
|
+
File.join(Azuki::Helpers.home_directory, ".azuki", "client")
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.latest_local_version
|
25
|
+
installed_version = client_version_from_path(installed_client_path)
|
26
|
+
updated_version = client_version_from_path(updated_client_path)
|
27
|
+
if compare_versions(updated_version, installed_version) > 0
|
28
|
+
updated_version
|
29
|
+
else
|
30
|
+
installed_version
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.client_version_from_path(path)
|
35
|
+
version_file = File.join(path, "lib/azuki/version.rb")
|
36
|
+
if File.exists?(version_file)
|
37
|
+
File.read(version_file).match(/VERSION = "([^"]+)"/)[1]
|
38
|
+
else
|
39
|
+
'0.0.0'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.disable(message=nil)
|
44
|
+
@disable = message if message
|
45
|
+
@disable
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.check_disabled!
|
49
|
+
if disable
|
50
|
+
Azuki::Helpers.error(disable)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.wait_for_lock(path, wait_for=5, check_every=0.5)
|
55
|
+
start = Time.now.to_i
|
56
|
+
while File.exists?(path)
|
57
|
+
sleep check_every
|
58
|
+
if (Time.now.to_i - start) > wait_for
|
59
|
+
Azuki::Helpers.error "Unable to acquire update lock"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
begin
|
63
|
+
FileUtils.touch path
|
64
|
+
ret = yield
|
65
|
+
ensure
|
66
|
+
FileUtils.rm_f path
|
67
|
+
end
|
68
|
+
ret
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.autoupdate?
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.update(url, autoupdate=false)
|
76
|
+
wait_for_lock(updating_lock_path, 5) do
|
77
|
+
require "excon"
|
78
|
+
require "azuki"
|
79
|
+
require "azuki/excon"
|
80
|
+
require "tmpdir"
|
81
|
+
require "zip/zip"
|
82
|
+
|
83
|
+
latest_version = Excon.get_with_redirect("http://assets.azukiapp.com/azuki-client/VERSION", :nonblock => false).body.chomp
|
84
|
+
|
85
|
+
if compare_versions(latest_version, latest_local_version) > 0
|
86
|
+
Dir.mktmpdir do |download_dir|
|
87
|
+
File.open("#{download_dir}/azuki.zip", "wb") do |file|
|
88
|
+
file.print Excon.get_with_redirect(url, :nonblock => false).body
|
89
|
+
end
|
90
|
+
|
91
|
+
hash = Digest::SHA256.file("#{download_dir}/azuki.zip").hexdigest
|
92
|
+
official_hash = Excon.get_with_redirect("https://toolbelt.azukiapp.com/update/hash", :nonblock => false).body.chomp
|
93
|
+
|
94
|
+
error "Update hash signature mismatch" unless hash == official_hash
|
95
|
+
|
96
|
+
Zip::ZipFile.open("#{download_dir}/azuki.zip") do |zip|
|
97
|
+
zip.each do |entry|
|
98
|
+
target = File.join(download_dir, entry.to_s)
|
99
|
+
FileUtils.mkdir_p File.dirname(target)
|
100
|
+
zip.extract(entry, target) { true }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
FileUtils.rm "#{download_dir}/azuki.zip"
|
105
|
+
|
106
|
+
old_version = latest_local_version
|
107
|
+
new_version = client_version_from_path(download_dir)
|
108
|
+
|
109
|
+
if compare_versions(new_version, old_version) < 0 && !autoupdate
|
110
|
+
Azuki::Helpers.error("Installed version (#{old_version}) is newer than the latest available update (#{new_version})")
|
111
|
+
end
|
112
|
+
|
113
|
+
FileUtils.rm_rf updated_client_path
|
114
|
+
FileUtils.mkdir_p File.dirname(updated_client_path)
|
115
|
+
FileUtils.cp_r download_dir, updated_client_path
|
116
|
+
|
117
|
+
new_version
|
118
|
+
end
|
119
|
+
else
|
120
|
+
false # already up to date
|
121
|
+
end
|
122
|
+
end
|
123
|
+
ensure
|
124
|
+
FileUtils.rm_f(updating_lock_path)
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.compare_versions(first_version, second_version)
|
128
|
+
first_version.split('.').map {|part| Integer(part) rescue part} <=> second_version.split('.').map {|part| Integer(part) rescue part}
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.inject_libpath
|
132
|
+
old_version = client_version_from_path(installed_client_path)
|
133
|
+
new_version = client_version_from_path(updated_client_path)
|
134
|
+
|
135
|
+
if compare_versions(new_version, old_version) > 0
|
136
|
+
$:.unshift File.join(updated_client_path, "lib")
|
137
|
+
vendored_gems = Dir[File.join(updated_client_path, "vendor", "gems", "*")]
|
138
|
+
vendored_gems.each do |vendored_gem|
|
139
|
+
$:.unshift File.join(vendored_gem, "lib")
|
140
|
+
end
|
141
|
+
load('azuki/updater.rb') # reload updated updater
|
142
|
+
end
|
143
|
+
|
144
|
+
background_update!
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.last_autoupdate_path
|
148
|
+
File.join(Azuki::Helpers.home_directory, ".azuki", "autoupdate.last")
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.background_update!
|
152
|
+
# if we've updated in the last 300 seconds, dont try again
|
153
|
+
if File.exists?(last_autoupdate_path)
|
154
|
+
return if (Time.now.to_i - File.mtime(last_autoupdate_path).to_i) < 300
|
155
|
+
end
|
156
|
+
log_path = File.join(Azuki::Helpers.home_directory, '.azuki', 'autoupdate.log')
|
157
|
+
FileUtils.mkdir_p File.dirname(log_path)
|
158
|
+
azuki_binary = File.expand_path($0)
|
159
|
+
pid = if defined?(RUBY_VERSION) and RUBY_VERSION =~ /^1\.8\.\d+/
|
160
|
+
fork do
|
161
|
+
exec("\"#{azuki_binary}\" update &> #{log_path} 2>&1")
|
162
|
+
end
|
163
|
+
else
|
164
|
+
spawn("\"#{azuki_binary}\" update", {:err => log_path, :out => log_path})
|
165
|
+
end
|
166
|
+
Process.detach(pid)
|
167
|
+
FileUtils.mkdir_p File.dirname(last_autoupdate_path)
|
168
|
+
FileUtils.touch last_autoupdate_path
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|