MrMurano 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +30 -0
- data/Gemfile +9 -0
- data/MrMurano.gemspec +35 -0
- data/README.markdown +27 -0
- data/Rakefile +25 -0
- data/TODO.taskpaper +45 -0
- data/bin/mr +66 -0
- data/lib/MrMurano/Account.rb +176 -0
- data/lib/MrMurano/Solution-Endpoint.rb +107 -0
- data/lib/MrMurano/Solution-File.rb +137 -0
- data/lib/MrMurano/Solution-ServiceConfig.rb +86 -0
- data/lib/MrMurano/Solution-Services.rb +163 -0
- data/lib/MrMurano/Solution-Users.rb +123 -0
- data/lib/MrMurano/Solution.rb +318 -0
- data/lib/MrMurano/configFile.rb +194 -0
- data/lib/MrMurano/hash.rb +18 -0
- data/lib/MrMurano/shelledCommand.rb +43 -0
- data/lib/MrMurano/status.rb +106 -0
- data/lib/MrMurano/sync.rb +132 -0
- data/lib/MrMurano/version.rb +4 -0
- data/lib/MrMurano.rb +13 -0
- metadata +166 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 907d8f71d784965567eff90d5ca1d88e29e08b93
|
4
|
+
data.tar.gz: c97c0feb26e1f54898a8c24da2d1e84d30e7a6c0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 436e65b69681a6df929bcbd8fe8a55842363dd6f13abae114d35209d59d83c575d082e8fd01737693d368a725defd8116b6c1aa59e638c5e566a6f39121bce4a
|
7
|
+
data.tar.gz: 639221f5cb46bea583e71d3945e6a3b74043548fef47dea5b234e7dae232ca1a02db0c264a31150130947fb4e5802e8bb581e648a549e8ac571add4e15a57c09
|
data/.gitignore
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
.DS_Store
|
2
|
+
.AppleDouble
|
3
|
+
.LSOverride
|
4
|
+
Icon
|
5
|
+
*.sw[a-z]
|
6
|
+
cookies
|
7
|
+
.jiraProject
|
8
|
+
.rpjProject
|
9
|
+
tags
|
10
|
+
|
11
|
+
xcuserdata
|
12
|
+
Pods/
|
13
|
+
pkg/
|
14
|
+
|
15
|
+
.mrmuranorc
|
16
|
+
files/
|
17
|
+
endpoints/
|
18
|
+
modules/
|
19
|
+
eventhandlers/
|
20
|
+
roles.yaml
|
21
|
+
users.yaml
|
22
|
+
|
23
|
+
# Thumbnails
|
24
|
+
._*
|
25
|
+
|
26
|
+
# Files that might appear on external disk
|
27
|
+
.Spotlight-V100
|
28
|
+
.Trashes
|
29
|
+
|
30
|
+
Gemfile.lock
|
data/Gemfile
ADDED
data/MrMurano.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'mrmurano/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'MrMurano'
|
7
|
+
s.version = MrMurano::VERSION
|
8
|
+
s.authors = ['Michael Conrad Tadpol Tilstra']
|
9
|
+
s.email = ['tadpol@tadpol.org']
|
10
|
+
s.license = 'MIT'
|
11
|
+
s.homepage = 'https://github.com/tadpol/MrMurano'
|
12
|
+
s.summary = 'Do more from the command line with Murano'
|
13
|
+
s.description = %{Do more from the command line with Murano
|
14
|
+
|
15
|
+
Push and pull data from Murano.
|
16
|
+
Get status on what things have changed.
|
17
|
+
See a diff of the changes before you push.
|
18
|
+
}
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
23
|
+
s.require_paths = ['lib']
|
24
|
+
|
25
|
+
s.add_runtime_dependency('commander', '~> 4.4.0')
|
26
|
+
s.add_runtime_dependency('inifile', '~> 3.0')
|
27
|
+
s.add_runtime_dependency('netrc', '~> 0.11.0')
|
28
|
+
s.add_runtime_dependency('http-form_data', '~> 1.0.1')
|
29
|
+
|
30
|
+
s.add_development_dependency('bundler', '~> 1.7.6')
|
31
|
+
s.add_development_dependency('rspec', '~> 3.2')
|
32
|
+
s.add_development_dependency('rake')
|
33
|
+
end
|
34
|
+
|
35
|
+
|
data/README.markdown
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# MrMurano
|
2
|
+
|
3
|
+
Do more from the command line with [Murano](https://exosite.com/platform/)
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
To start from an existing project in Murano
|
8
|
+
```
|
9
|
+
mkdir myproject
|
10
|
+
cd myproject
|
11
|
+
mr syncdown --all
|
12
|
+
```
|
13
|
+
|
14
|
+
Do stuff, see what changed: `mr status --all` or `mr diff -same`.
|
15
|
+
Then deploy `mr syncup --all`
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
## Install
|
20
|
+
|
21
|
+
Source install for now.
|
22
|
+
```
|
23
|
+
git clone … MrMurano
|
24
|
+
cd MrMurano
|
25
|
+
rake install
|
26
|
+
```
|
27
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
#task :default => []
|
4
|
+
|
5
|
+
# TODO: figure out better way to test.
|
6
|
+
desc "Install gem in user dir"
|
7
|
+
task :bob do
|
8
|
+
sh %{gem install --user-install pkg/MrMurano-#{Bundler::GemHelper.gemspec.version}.gem}
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Uninstall from user dir"
|
12
|
+
task :unbob do
|
13
|
+
sh %{gem uninstall --user-install pkg/MrMurano-#{Bundler::GemHelper.gemspec.version}.gem}
|
14
|
+
end
|
15
|
+
|
16
|
+
task :echo do
|
17
|
+
puts "= #{Bundler::GemHelper.gemspec.version} ="
|
18
|
+
end
|
19
|
+
|
20
|
+
task :run do
|
21
|
+
sh %{ruby -Ilib bin/mr }
|
22
|
+
end
|
23
|
+
|
24
|
+
# vim: set sw=4 ts=4 :
|
25
|
+
|
data/TODO.taskpaper
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
Commands:
|
3
|
+
- Add Diff Command @done(2016-07-27)
|
4
|
+
|
5
|
+
Endpoints:
|
6
|
+
- Add support for multiple endpoints in one file
|
7
|
+
- Add directory support like in modules @done(2016-07-26)
|
8
|
+
|
9
|
+
Files:
|
10
|
+
- Fix upload. @done(2016-08-01)
|
11
|
+
- Files won't update, they always delete then add. @done(2016-07-28)
|
12
|
+
|
13
|
+
Users:
|
14
|
+
Much of this is stuck until we get more docs on the User/Role management
|
15
|
+
- Figure out how to upload (create and update) user info.
|
16
|
+
- Figure out how to add Roles to Users in the local data and upload it.
|
17
|
+
- Fix diff for Users and Roles.
|
18
|
+
- Have hash keys in the yaml be strings not symbols. (don't start with colon) @done(2016-07-27)
|
19
|
+
|
20
|
+
Product:
|
21
|
+
- Need to add way to set the product ID on a device eventhandler. @done(2016-08-01)
|
22
|
+
|
23
|
+
Config:
|
24
|
+
- Think about adding dev,staging,prod system; how would that work?
|
25
|
+
|
26
|
+
SolutionBase:
|
27
|
+
- All network traffic is serialized. Make some parallel.
|
28
|
+
- Rebuild how local names and paths are computed from remote items. @done(2016-07-27)
|
29
|
+
|
30
|
+
Bundles:
|
31
|
+
- Work on design
|
32
|
+
Thinking of something like VIM bundles. A directory of directories. Each with a
|
33
|
+
manafest file? (maybe) A Bundle is a group of modules, endpoints, static files
|
34
|
+
and the other things.
|
35
|
+
|
36
|
+
There needs to be some layering logic added, where the bundles are stacked and
|
37
|
+
then the top-level files are stack on top of that. This builds the final map of
|
38
|
+
what gets uploaded to the server.
|
39
|
+
|
40
|
+
For syncdown, bundles are considered to be read-only.
|
41
|
+
|
42
|
+
The goal is to have things like Users or Debug that you just include into a
|
43
|
+
project. And it gives all of the library, routes, statics and whatnot that you
|
44
|
+
need.
|
45
|
+
|
data/bin/mr
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'commander/import'
|
5
|
+
require 'pathname'
|
6
|
+
require 'MrMurano'
|
7
|
+
require 'pp'
|
8
|
+
|
9
|
+
program :version, MrMurano::VERSION
|
10
|
+
program :description, %{Manage a Solution in Exosite's Murano}
|
11
|
+
|
12
|
+
global_option('-V', '--verbose', 'Be chatty') {
|
13
|
+
$cfg['tool.verbose'] = true
|
14
|
+
}
|
15
|
+
global_option('-n', '--dry', %{Don't run actions that make changes}) {
|
16
|
+
$cfg['tool.dry'] = true
|
17
|
+
$cfg['tool.verbose'] = true # dry implies verbose
|
18
|
+
}
|
19
|
+
global_option '--skip-plugins', %{Don't load plugins. Good for when one goes bad.}
|
20
|
+
|
21
|
+
global_option('-C', '--configfile FILE', %{Load additional configuration file}) {|file|
|
22
|
+
# this is called after all of the top level code in this file.
|
23
|
+
$cfg.load_specific(file)
|
24
|
+
}
|
25
|
+
global_option('-c', '--config KEY=VALUE', %{Set a single config key}) {|param|
|
26
|
+
key, value = param.split('=', 2)
|
27
|
+
raise "Bad config '#{param}'" if key.nil?
|
28
|
+
$cfg[key] = value
|
29
|
+
}
|
30
|
+
|
31
|
+
default_command :help
|
32
|
+
#default_command :syncup
|
33
|
+
|
34
|
+
$cfg = MrMurano::Config.new
|
35
|
+
$cfg.load
|
36
|
+
|
37
|
+
# Basic command support is:
|
38
|
+
# - read/write config file in [Project, User, System] (all are optional)
|
39
|
+
# - Introspection for tab completion.
|
40
|
+
# - Look for tools in PATH that are +x and "mr-foo..."
|
41
|
+
|
42
|
+
|
43
|
+
# Look for plug-ins
|
44
|
+
pgds = [
|
45
|
+
Pathname.new(Dir.home) + '.mrmurano' + 'plugins'
|
46
|
+
]
|
47
|
+
# Add plugin dirs from configs
|
48
|
+
# This is run before the command line options are parsed, so need to check old way.
|
49
|
+
if not ARGV.include? '--skip-plugins' then
|
50
|
+
pgds << Pathname.new(ENV['MR_MURANO_PLUGIN_DIR']) if ENV.has_key? 'MR_MURANO_PLUGIN_DIR'
|
51
|
+
pgds.each do |path|
|
52
|
+
next unless path.exist?
|
53
|
+
path.each_child do |plugin|
|
54
|
+
next if plugin.directory?
|
55
|
+
next unless plugin.readable?
|
56
|
+
next if plugin.basename.fnmatch('.*') # don't read anything starting with .
|
57
|
+
begin
|
58
|
+
require plugin.to_s
|
59
|
+
rescue Exception => e
|
60
|
+
$stderr.puts "Failed to load plugin at #{plugin} because #{e}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# vim: set ai et sw=2 ts=2 :
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'netrc'
|
2
|
+
require 'uri'
|
3
|
+
require 'net/http'
|
4
|
+
require 'json'
|
5
|
+
require 'date'
|
6
|
+
require 'pp'
|
7
|
+
require 'terminal-table'
|
8
|
+
|
9
|
+
module MrMurano
|
10
|
+
class Account
|
11
|
+
|
12
|
+
|
13
|
+
def endPoint(path)
|
14
|
+
URI('https://' + $cfg['net.host'] + '/api:1/' + path.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
def _password
|
18
|
+
host = $cfg['net.host']
|
19
|
+
user = $cfg['user.name']
|
20
|
+
if user.nil? then
|
21
|
+
user = ask("Account name: ")
|
22
|
+
$cfg['user.name'] = user
|
23
|
+
end
|
24
|
+
# Maybe in the future use Keychain. For now all in Netrc.
|
25
|
+
# if (/darwin/ =~ RUBY_PLATFORM) != nil then
|
26
|
+
# # macOS
|
27
|
+
# pws = `security 2>&1 >/dev/null find-internet-password -gs "#{host}" -a "#{user}"`
|
28
|
+
# pws.strip!
|
29
|
+
# pws.sub!(/^password: "(.*)"$/, '\1')
|
30
|
+
# return pws
|
31
|
+
# Use Netrc
|
32
|
+
nrc = Netrc.read
|
33
|
+
ruser, pws = nrc[host]
|
34
|
+
pws = nil unless ruser == user
|
35
|
+
if pws.nil? then
|
36
|
+
pws = ask("Password: ") { |q| q.echo = "*" }
|
37
|
+
nrc[host] = user, pws
|
38
|
+
nrc.save
|
39
|
+
end
|
40
|
+
pws
|
41
|
+
end
|
42
|
+
|
43
|
+
def token
|
44
|
+
if @token.nil? then
|
45
|
+
r = endPoint('token/')
|
46
|
+
Net::HTTP.start(r.host, r.port, :use_ssl=>true) do |http|
|
47
|
+
request = Net::HTTP::Post.new(r)
|
48
|
+
request.content_type = 'application/json'
|
49
|
+
#request.basic_auth(username(), password())
|
50
|
+
request.body = JSON.generate({
|
51
|
+
:email => $cfg['user.name'],
|
52
|
+
:password => _password
|
53
|
+
})
|
54
|
+
|
55
|
+
response = http.request(request)
|
56
|
+
case response
|
57
|
+
when Net::HTTPSuccess
|
58
|
+
token = JSON.parse(response.body)
|
59
|
+
@token = token['token']
|
60
|
+
else
|
61
|
+
say_error "No token! because: #{response}"
|
62
|
+
@token = nil
|
63
|
+
raise response
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
@token
|
68
|
+
end
|
69
|
+
|
70
|
+
def businesses
|
71
|
+
r = endPoint('user/' + $cfg['user.name'] + '/membership/')
|
72
|
+
Net::HTTP.start(r.host, r.port, :use_ssl=>true) do |http|
|
73
|
+
request = Net::HTTP::Get.new(r)
|
74
|
+
request.content_type = 'application/json'
|
75
|
+
request['authorization'] = 'token ' + token
|
76
|
+
|
77
|
+
response = http.request(request)
|
78
|
+
case response
|
79
|
+
when Net::HTTPSuccess
|
80
|
+
busy = JSON.parse(response.body)
|
81
|
+
return busy
|
82
|
+
else
|
83
|
+
raise response
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def products
|
89
|
+
r = endPoint('business/' + $cfg['business.id'] + '/product/')
|
90
|
+
Net::HTTP.start(r.host, r.port, :use_ssl=>true) do |http|
|
91
|
+
request = Net::HTTP::Get.new(r)
|
92
|
+
request.content_type = 'application/json'
|
93
|
+
request['authorization'] = 'token ' + token
|
94
|
+
|
95
|
+
response = http.request(request)
|
96
|
+
case response
|
97
|
+
when Net::HTTPSuccess
|
98
|
+
busy = JSON.parse(response.body)
|
99
|
+
return busy
|
100
|
+
else
|
101
|
+
raise response
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def solutions
|
107
|
+
r = endPoint('business/' + $cfg['business.id'] + '/solution/')
|
108
|
+
Net::HTTP.start(r.host, r.port, :use_ssl=>true) do |http|
|
109
|
+
request = Net::HTTP::Get.new(r)
|
110
|
+
request.content_type = 'application/json'
|
111
|
+
request['authorization'] = 'token ' + token
|
112
|
+
|
113
|
+
response = http.request(request)
|
114
|
+
case response
|
115
|
+
when Net::HTTPSuccess
|
116
|
+
busy = JSON.parse(response.body)
|
117
|
+
return busy
|
118
|
+
else
|
119
|
+
raise response
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# This is largely for testing.
|
128
|
+
command :account do |c|
|
129
|
+
c.syntax = %{mr account ...}
|
130
|
+
c.description = %{Show things about your account.}
|
131
|
+
c.option '--businesses', 'Get businesses for user'
|
132
|
+
c.option '--products', 'Get products for user (needs a business)'
|
133
|
+
c.option '--solutions', 'Get solutions for user (needs a business)'
|
134
|
+
c.option '--idonly', 'Only return the ids'
|
135
|
+
|
136
|
+
c.action do |args, options|
|
137
|
+
|
138
|
+
acc = MrMurano::Account.new
|
139
|
+
|
140
|
+
if options.businesses then
|
141
|
+
data = acc.businesses
|
142
|
+
if options.idonly then
|
143
|
+
say data.map{|row| row['bizid']}.join(' ')
|
144
|
+
else
|
145
|
+
busy = data.map{|row| [row['bizid'], row['role'], row['name']]}
|
146
|
+
table = Terminal::Table.new :rows => busy, :headings => ['Biz ID', 'Role', 'Name']
|
147
|
+
say table
|
148
|
+
end
|
149
|
+
|
150
|
+
elsif options.products then
|
151
|
+
data = acc.products
|
152
|
+
if options.idonly then
|
153
|
+
say data.map{|row| row['pid']}.join(' ')
|
154
|
+
else
|
155
|
+
busy = data.map{|r| [r['label'], r['type'], r['pid'], r['modelId']]}
|
156
|
+
table = Terminal::Table.new :rows => busy, :headings => ['Label', 'Type', 'PID', 'ModelID']
|
157
|
+
say table
|
158
|
+
end
|
159
|
+
|
160
|
+
elsif options.solutions then
|
161
|
+
data = acc.solutions
|
162
|
+
if options.idonly then
|
163
|
+
say data.map{|row| row['apiId']}.join(' ')
|
164
|
+
else
|
165
|
+
busy = data.map{|r| [r['apiId'], r['domain'], r['type'], r['sid']]}
|
166
|
+
table = Terminal::Table.new :rows => busy, :headings => ['API ID', 'Domain', 'Type', 'SID']
|
167
|
+
say table
|
168
|
+
end
|
169
|
+
|
170
|
+
else
|
171
|
+
say acc.token
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|
176
|
+
# vim: set ai et sw=2 ts=2 :
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
require 'pp'
|
5
|
+
|
6
|
+
module MrMurano
|
7
|
+
# …/endpoint
|
8
|
+
class Endpoint < SolutionBase
|
9
|
+
def initialize
|
10
|
+
super
|
11
|
+
@uriparts << 'endpoint'
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# This gets all data about all endpoints
|
16
|
+
def list
|
17
|
+
get()
|
18
|
+
end
|
19
|
+
|
20
|
+
def fetch(id)
|
21
|
+
ret = get('/' + id.to_s)
|
22
|
+
aheader = ret['script'].lines.first.chomp
|
23
|
+
dheader = /^--#ENDPOINT (?i:#{ret['method']}) #{ret['path']}$/
|
24
|
+
if block_given? then
|
25
|
+
yield dheader + "\n" unless dheader =~ aheader
|
26
|
+
yield ret['script']
|
27
|
+
else
|
28
|
+
res = ''
|
29
|
+
res << dheader + "\n" unless dheader =~ aheader
|
30
|
+
res << ret['script']
|
31
|
+
res
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Upload endpoint
|
37
|
+
# :local path to file to push
|
38
|
+
# :remote hash of method and endpoint path
|
39
|
+
def upload(local, remote)
|
40
|
+
local = Pathname.new(local) unless local.kind_of? Pathname
|
41
|
+
raise "no file" unless local.exist?
|
42
|
+
|
43
|
+
# we assume these are small enough to slurp.
|
44
|
+
script = local.read
|
45
|
+
limitkeys = [:method, :path, :script, @itemkey]
|
46
|
+
remote = remote.select{|k,v| limitkeys.include? k }
|
47
|
+
remote[:script] = script
|
48
|
+
# post('', remote)
|
49
|
+
if remote.has_key? @itemkey then
|
50
|
+
put('/' + remote[@itemkey], remote) do |request, http|
|
51
|
+
response = http.request(request)
|
52
|
+
case response
|
53
|
+
when Net::HTTPSuccess
|
54
|
+
#return JSON.parse(response.body)
|
55
|
+
when Net::HTTPNotFound
|
56
|
+
verbose "\tDoesn't exist, creating"
|
57
|
+
post('/', remote)
|
58
|
+
else
|
59
|
+
say_error "got #{response} from #{request} #{request.uri.to_s}"
|
60
|
+
say_error ":: #{response.body}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
else
|
64
|
+
verbose "\tNo itemkey, creating"
|
65
|
+
post('/', remote)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Delete an endpoint
|
71
|
+
def remove(id)
|
72
|
+
delete('/' + id.to_s)
|
73
|
+
end
|
74
|
+
|
75
|
+
def tolocalname(item, key)
|
76
|
+
name = item[:method].downcase
|
77
|
+
name << '_'
|
78
|
+
name << item[:path].gsub(/\//, '-')
|
79
|
+
name << '.lua'
|
80
|
+
end
|
81
|
+
|
82
|
+
def toremotename(from, path)
|
83
|
+
# read first line of file and get method/path from it?
|
84
|
+
path = Pathname.new(path) unless path.kind_of? Pathname
|
85
|
+
aheader = path.readlines().first
|
86
|
+
md = /--#ENDPOINT (\S+) (.*)/.match(aheader)
|
87
|
+
raise "Not an Endpoint: #{path.to_s}" if md.nil?
|
88
|
+
{:method=>md[1], :path=>md[2]}
|
89
|
+
end
|
90
|
+
|
91
|
+
def synckey(item)
|
92
|
+
"#{item[:method].upcase}_#{item[:path]}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def docmp(itemA, itemB)
|
96
|
+
if itemA[:script].nil? and itemA[:local_path] then
|
97
|
+
itemA[:script] = itemA[:local_path].read
|
98
|
+
end
|
99
|
+
if itemB[:script].nil? and itemB[:local_path] then
|
100
|
+
itemB[:script] = itemB[:local_path].read
|
101
|
+
end
|
102
|
+
return itemA[:script] != itemB[:script]
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
# vim: set ai et sw=2 ts=2 :
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require "http/form_data"
|
4
|
+
require 'digest/sha1'
|
5
|
+
require 'mime/types'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
module MrMurano
|
9
|
+
# …/file
|
10
|
+
class File < SolutionBase
|
11
|
+
def initialize
|
12
|
+
super
|
13
|
+
@uriparts << 'file'
|
14
|
+
@itemkey = :path
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Get a list of all of the static content
|
19
|
+
def list
|
20
|
+
get()
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Get one item of the static content.
|
25
|
+
def fetch(path, &block)
|
26
|
+
get(path) do |request, http|
|
27
|
+
http.request(request) do |resp|
|
28
|
+
case resp
|
29
|
+
when Net::HTTPSuccess
|
30
|
+
if block_given? then
|
31
|
+
resp.read_body &block
|
32
|
+
else
|
33
|
+
resp.read_body do |chunk|
|
34
|
+
$stdout.write chunk
|
35
|
+
end
|
36
|
+
end
|
37
|
+
else
|
38
|
+
say_error "got #{resp.to_s} from #{request} #{request.uri.to_s}"
|
39
|
+
raise resp
|
40
|
+
end
|
41
|
+
end
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Delete a file
|
48
|
+
def remove(path)
|
49
|
+
# TODO test
|
50
|
+
delete('/'+path)
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Upload a file
|
55
|
+
def upload(local, remote)
|
56
|
+
local = Pathname.new(local) unless local.kind_of? Pathname
|
57
|
+
|
58
|
+
uri = endPoint('upload' + remote[:path])
|
59
|
+
# kludge past for a bit.
|
60
|
+
#`curl -s -H 'Authorization: token #{@token}' '#{uri.to_s}' -F file=@#{local.to_s}`
|
61
|
+
|
62
|
+
# http://stackoverflow.com/questions/184178/ruby-how-to-post-a-file-via-http-as-multipart-form-data
|
63
|
+
#
|
64
|
+
# Look at: https://github.com/httprb/http
|
65
|
+
# If it works well, consider porting over to it.
|
66
|
+
#
|
67
|
+
# Or just: https://github.com/httprb/form_data.rb ?
|
68
|
+
#
|
69
|
+
# Most of these pull into ram. So maybe just go with that. Would guess that
|
70
|
+
# truely large static content is rare, and we can optimize/fix that later.
|
71
|
+
|
72
|
+
form = HTTP::FormData.create(:file=>HTTP::FormData::File.new(local.to_s))
|
73
|
+
req = Net::HTTP::Put.new(uri)
|
74
|
+
workit(req) do |request,http|
|
75
|
+
request.content_type = form.content_type
|
76
|
+
request.content_length = form.content_length
|
77
|
+
request.body = form.to_s
|
78
|
+
|
79
|
+
response = http.request(request)
|
80
|
+
case response
|
81
|
+
when Net::HTTPSuccess
|
82
|
+
else
|
83
|
+
say_error "got #{response} from #{request} #{request.uri.to_s}"
|
84
|
+
say_error ":: #{response.body}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def tolocalname(item, key)
|
90
|
+
name = item[key]
|
91
|
+
name = $cfg['files.default_page'] if name == '/'
|
92
|
+
name
|
93
|
+
end
|
94
|
+
|
95
|
+
def toremotename(from, path)
|
96
|
+
name = super(from, path)
|
97
|
+
name = '/' if name == $cfg['files.default_page']
|
98
|
+
name = "/#{name}" unless name.chars.first == '/'
|
99
|
+
|
100
|
+
mime = MIME::Types.type_for(path.to_s)[0] || MIME::Types["application/octet-stream"][0]
|
101
|
+
|
102
|
+
sha1 = Digest::SHA1.file(path.to_s).hexdigest
|
103
|
+
|
104
|
+
{:path=>name, :mime_type=>mime.simplified, :checksum=>sha1}
|
105
|
+
end
|
106
|
+
|
107
|
+
def synckey(item)
|
108
|
+
item[:path]
|
109
|
+
end
|
110
|
+
|
111
|
+
def docmp(itemA, itemB)
|
112
|
+
return (itemA[:mime_type] != itemB[:mime_type] or
|
113
|
+
itemA[:checksum] != itemB[:checksum])
|
114
|
+
end
|
115
|
+
|
116
|
+
def locallist(from)
|
117
|
+
from = Pathname.new(from) unless from.kind_of? Pathname
|
118
|
+
unless from.exist? then
|
119
|
+
return []
|
120
|
+
end
|
121
|
+
raise "Not a directory: #{from.to_s}" unless from.directory?
|
122
|
+
|
123
|
+
Pathname.glob(from.to_s + '/**/*').map do |path|
|
124
|
+
name = toremotename(from, path)
|
125
|
+
case name
|
126
|
+
when Hash
|
127
|
+
name[:local_path] = path
|
128
|
+
name
|
129
|
+
else
|
130
|
+
{:local_path => path, :name => name}
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
137
|
+
# vim: set ai et sw=2 ts=2 :
|