MrMurano 1.0.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 +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 :
|