kenai_tools 0.0.7

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.
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+
6
+ # RubyMine per-user files http://devnet.jetbrains.net/docs/DOC-1192
7
+ .idea/workspace.xml
8
+ .idea/tasks.xml
data/.idea/.name ADDED
@@ -0,0 +1 @@
1
+ kenai_tools
data/.idea/.rakeTasks ADDED
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Settings><!--This file was automatically generated by Ruby plugin.
3
+ You are allowed to:
4
+ 1. Remove rake task
5
+ 2. Add existing rake tasks
6
+ To add existing rake tasks automatically delete this file and reload the project.
7
+ --><RakeGroup description="" fullCmd="" taksId="rake"><RakeTask description="Build kenai_tools-0.0.1.gem into the pkg directory" fullCmd="build" taksId="build" /><RakeTask description="Build and install kenai_tools-0.0.1.gem into system gems" fullCmd="install" taksId="install" /><RakeTask description="Create tag v0.0.1 and build and push kenai_tools-0.0.1.gem to Rubygems" fullCmd="release" taksId="release" /></RakeGroup></Settings>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
4
+ </project>
5
+
@@ -0,0 +1,40 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="RUBY_MODULE" version="4">
3
+ <component name="GemRequirementsHolder" version="3">
4
+ <requirement>
5
+ <requirement>
6
+ <dependency name="highline" version="1.6" bound="LATEST_IN_BRANCH" git="false" path="false" doRequire="true" />
7
+ </requirement>
8
+ <source from="Gemfile" />
9
+ </requirement>
10
+ <requirement>
11
+ <requirement>
12
+ <dependency name="json" version="1.5" bound="LATEST_IN_BRANCH" git="false" path="false" doRequire="true" />
13
+ </requirement>
14
+ <source from="Gemfile" />
15
+ </requirement>
16
+ <requirement>
17
+ <requirement>
18
+ <dependency name="rest-client" version="1.6" bound="LATEST_IN_BRANCH" git="false" path="false" doRequire="true" />
19
+ </requirement>
20
+ <source from="Gemfile" />
21
+ </requirement>
22
+ </component>
23
+ <component name="NewModuleRootManager">
24
+ <content url="file://$MODULE_DIR$" />
25
+ <orderEntry type="inheritedJdk" />
26
+ <orderEntry type="sourceFolder" forTests="false" />
27
+ <orderEntry type="library" scope="PROVIDED" name="rspec (v2.5.0, RVM: ruby-1.8.7-p302 [kenai_tools]) [gem]" level="application" />
28
+ <orderEntry type="library" scope="PROVIDED" name="rest-client (v1.6.3, RVM: ruby-1.8.7-p302 [kenai_tools]) [gem]" level="application" />
29
+ <orderEntry type="library" scope="PROVIDED" name="rspec-core (v2.5.2, RVM: ruby-1.8.7-p302 [kenai_tools]) [gem]" level="application" />
30
+ <orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.1.2, RVM: ruby-1.8.7-p302 [kenai_tools]) [gem]" level="application" />
31
+ <orderEntry type="library" scope="PROVIDED" name="highline (v1.6.2, RVM: ruby-1.8.7-p302 [kenai_tools]) [gem]" level="application" />
32
+ <orderEntry type="library" scope="PROVIDED" name="bundler (v1.0.12, RVM: ruby-1.8.7-p302 [kenai_tools]) [gem]" level="application" />
33
+ <orderEntry type="library" scope="PROVIDED" name="mime-types (v1.16, RVM: ruby-1.8.7-p302 [kenai_tools]) [gem]" level="application" />
34
+ <orderEntry type="library" scope="PROVIDED" name="json (v1.5.3, RVM: ruby-1.8.7-p302 [kenai_tools]) [gem]" level="application" />
35
+ <orderEntry type="library" scope="PROVIDED" name="gemcutter (v0.7.0, RVM: ruby-1.8.7-p302 [kenai_tools]) [gem]" level="application" />
36
+ <orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v2.5.0, RVM: ruby-1.8.7-p302 [kenai_tools]) [gem]" level="application" />
37
+ <orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v2.5.0, RVM: ruby-1.8.7-p302 [kenai_tools]) [gem]" level="application" />
38
+ </component>
39
+ </module>
40
+
data/.idea/misc.xml ADDED
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="DependencyValidationManager">
4
+ <option name="SKIP_IMPORT_STATEMENTS" value="false" />
5
+ </component>
6
+ <component name="ProjectRootManager" version="2" project-jdk-name="RVM: ruby-1.8.7-p302 [kenai_tools]" project-jdk-type="RUBY_SDK" />
7
+ </project>
8
+
data/.idea/modules.xml ADDED
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/kenai_tools.iml" filepath="$PROJECT_DIR$/.idea/kenai_tools.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
9
+
data/.idea/vcs.xml ADDED
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
7
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in kenai_tools.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Kenai Tools
2
+
3
+ ## Description
4
+
5
+ The kenai_tools gem provides command line tools to access web services for
6
+ web sites that are hosted on the Kenai application platform. Currently,
7
+ there is a dlutil tool that project admins can use to manage the downloads
8
+ feature of a project. For example, they can perform bulk uploads and
9
+ downloads of files to a site hosted on Kenai such as java.net.
10
+
11
+ ## Installation
12
+
13
+ $ gem install kenai_tools
14
+
15
+ or
16
+
17
+ $ sudo gem install kenai_tools
18
+
19
+ ## Usage and documentation
20
+
21
+ Please see the command usage information.
22
+
23
+ $ dlutil --help
24
+
25
+ ## Problems
26
+
27
+ Some dlutil commands do not work on Windows so a unix-based OS is
28
+ recommended.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/bin/dlutil ADDED
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require "bundler/setup"
5
+
6
+ $LOAD_PATH << File.dirname(__FILE__) + '/../lib'
7
+ require 'ostruct'
8
+ require 'optparse'
9
+ require 'highline/import'
10
+ require 'kenai_tools'
11
+
12
+ PROGRAM = File.basename(__FILE__)
13
+
14
+ # Array of command info. Keys are
15
+ # :name = command line name
16
+ # :call = name of method to call instead of :name, if specified
17
+ # :result = method produces a result
18
+ # :login = method requires authentication
19
+ # :core = core, i.e. main command
20
+ COMMANDS = [{:name => :ping, :result => true, :core => true},
21
+ {:name => :login, :call => :ping, :login => true, :result => true, :core => true},
22
+ {:name => :pull, :core => true},
23
+ {:name => :push, :login => true, :core => true},
24
+ {:name => :exist?, :result => true},
25
+ {:name => :entry_type, :result => true},
26
+ {:name => :mkdir, :login => true},
27
+ {:name => :rmdir, :login => true, :result => nil},
28
+ {:name => :rm, :login => true},
29
+ {:name => :rm_r, :login => true},
30
+ {:name => :delete_feature, :login => true},
31
+ {:name => :get_or_create, :login => true, :result => true},
32
+ {:name => :downloads_name, :result => true},
33
+ {:name => :downloads_feature, :result => true},
34
+ {:name => :ls, :result => true}]
35
+
36
+ options = OpenStruct.new
37
+ @opts = OptionParser.new do |opts|
38
+ opts.banner = "Usage: #{PROGRAM} [options] COMMAND [args] ..."
39
+
40
+ def wrap(s, width = 78)
41
+ s.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n")
42
+ end
43
+
44
+ str = COMMANDS.select { |info| info[:core] }.map { |info| info[:name] }.join(', ')
45
+ opts.separator "Main commands: #{wrap(str)}"
46
+ str = COMMANDS.reject { |info| info[:core] }.map { |info| info[:name] }.join(', ')
47
+ opts.separator "Other commands: #{wrap(str)}"
48
+ opts.separator "Note: Unix OS is recommended as some commands fail on Windows"
49
+ opts.separator "Examples:"
50
+ opts.separator " #{PROGRAM} -r kenai.com,my-project pull / my-project-downloads"
51
+ opts.separator " #{PROGRAM} -r java.net,my-project push dist/ /version-3.1"
52
+ opts.separator " #{PROGRAM} -r java.net,my-project push toolkit-1.2.3.jar /"
53
+ opts.separator " #{PROGRAM} -r java.net,glassfish login"
54
+ opts.separator " #{PROGRAM} -r java.net,my-project rm /toolkit-1.2.3.jar"
55
+ opts.separator " #{PROGRAM} -r java.net,my-project rm_r /version-3.1"
56
+ opts.separator ""
57
+ opts.separator "Specific options:"
58
+
59
+ def to_url(host_or_url)
60
+ host_or_url =~ %r{^http(s?)://} ? host_or_url : "https://#{host_or_url}/"
61
+ end
62
+
63
+ opts.on("-r", "--remote ENDPOINT_SPEC", "Remote endpoint SITE,PROJECT,[DOWNLOADS_NAME]",
64
+ " SITE may be simply HOST or http(s)?://HOST:PORT") do |v|
65
+ host, project, downloads_name = v.split(',')
66
+ options.remote_valid = host && project
67
+ options.host_url = to_url(host)
68
+ options.project = project
69
+ options.downloads_name = downloads_name
70
+ end
71
+
72
+ opts.on("-p", "--password-file FILE", "Read username/password (nl-separated) from FILE") do |v|
73
+ options.password_file = v
74
+ end
75
+
76
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
77
+ options.verbose = v
78
+ end
79
+
80
+ opts.on("-t", "--timeout SECS", "Timeout in seconds") do |v|
81
+ options.timeout = v
82
+ end
83
+
84
+ opts.on("--cloak-password PASSWORD", "Cloaking password") do |v|
85
+ options.cloak_password = v
86
+ end
87
+
88
+ opts.on_tail("-h", "--help", "Show this message") do
89
+ puts opts
90
+ exit
91
+ end
92
+ end
93
+
94
+ def usage
95
+ puts @opts
96
+ exit 1
97
+ end
98
+
99
+ begin
100
+ @opts.parse!
101
+ rescue
102
+ usage
103
+ end
104
+
105
+ usage unless command = ARGV.shift and
106
+ cmd_info = COMMANDS.find { |info| info[:name] == command.to_sym } and
107
+ options.remote_valid
108
+
109
+ def get_credentials(options, dlc_opts)
110
+ user, password = nil
111
+ if options.password_file
112
+ File.open(options.password_file) do |f|
113
+ user, password = f.read.split
114
+ end
115
+ else
116
+ say "Please enter your login credentials for #{options.host_url}"
117
+ user = ask('Username: ')
118
+ password = ask('Password: ') { |q| q.echo = '*' }
119
+ end
120
+ if user
121
+ dlc_opts[:user] = user
122
+ dlc_opts[:password] = password
123
+ end
124
+ end
125
+
126
+ dlc_opts = {:downloads_name => options.downloads_name}
127
+ if cmd_info[:login]
128
+ get_credentials(options, dlc_opts)
129
+ end
130
+ dlc_opts[:timeout] = options.timeout if options.timeout
131
+ dlc_opts[:cloak_password] = options.cloak_password if options.cloak_password
132
+ dlc_opts[:log] = $stderr if options.verbose
133
+ dlclient = KenaiTools::DownloadsClient.new(options.host_url, options.project, dlc_opts)
134
+
135
+ method_name = cmd_info[:call] ? cmd_info[:call] : cmd_info[:name]
136
+ result = dlclient.send(method_name, *ARGV)
137
+ if cmd_info[:result]
138
+ puts String === result ? result : result.inspect
139
+ end
@@ -0,0 +1,43 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "kenai_tools/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "kenai_tools"
7
+ s.version = KenaiTools::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Edwin Goei", "Project Kenai Team"]
10
+ s.email = ["edwin.goei@oracle.com"]
11
+ s.homepage = "http://kenai.com/projects/kenaiapis"
12
+ s.summary = %q{Tools for sites hosted on the Kenai platform. Use dlutil to upload and download files.}
13
+ s.description = %q{Tools for sites such as java.net that are hosted on the Kenai platform. Use dlutil to upload and download files.}
14
+ s.post_install_message = %q{
15
+ ==============================================================================
16
+
17
+ Thanks for installing kenai_tools. Run the following command for what to do
18
+ next:
19
+
20
+ dlutil --help
21
+
22
+ Warning: this tool is not yet supported on Windows. Please use a unix-based
23
+ OS. For more info, see http://kenai.com/jira/browse/KENAI-2853.
24
+
25
+ ==============================================================================
26
+
27
+
28
+ }
29
+
30
+ s.rubyforge_project = "kenai_tools"
31
+
32
+ s.files = `git ls-files`.split("\n")
33
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
34
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
35
+ s.require_paths = ["lib"]
36
+
37
+ s.add_development_dependency("rspec", "~> 2.5")
38
+ s.add_development_dependency("bundler", "~> 1.0")
39
+ s.add_development_dependency("gemcutter")
40
+ s.add_dependency("rest-client", "~> 1.6")
41
+ s.add_dependency("json", "~> 1.5")
42
+ s.add_dependency("highline", "~> 1.6")
43
+ end
@@ -0,0 +1,241 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'forwardable'
5
+ require 'rest_client'
6
+
7
+ module KenaiTools
8
+ # Path arguments to public methods of this API work with Pathname objects as well as Strings
9
+ class DownloadsClient
10
+ CONTENT_TYPE_KENAI_ENTRIES = "application/vnd.com.kenai.entries+json"
11
+
12
+ extend Forwardable
13
+ def_delegators :@kc, :authenticate, :authenticated?
14
+
15
+ attr_accessor :project, :cloak_password
16
+
17
+ #
18
+ # Local options are :downloads_name, :log, :cloak_password
19
+ # Other options such as :timeout are forwarded
20
+ # DownloadClient.new("https://kenai.com/", "jruby")
21
+ # DownloadClient.new("https://testkenai.com/", "my-project", :downloads_name => 'downloads2', :log => $stderr
22
+ # :timeout => 36000, :cloak_password => 'a_secret')
23
+ #
24
+ def initialize(site, project, opts = {})
25
+ @site = site
26
+ @project = project
27
+ @specified_downloads_name = opts.delete(:downloads_name)
28
+ @cloak_password = opts.delete(:cloak_password)
29
+ RestClient.log = opts.delete(:log)
30
+ @kc = KenaiClient.new(site, opts)
31
+ end
32
+
33
+ def get_or_create
34
+ unless ping
35
+ @specified_downloads_name = "downloads" unless @specified_downloads_name
36
+ params = {:feature => {:name => @specified_downloads_name, :service => "downloads",
37
+ :display_name => @specified_downloads_name.capitalize}}
38
+ @kc["projects/#{project}/features"].post(params, :content_type => 'application/json')
39
+ end
40
+ downloads_name
41
+ end
42
+
43
+ def delete_feature(confirm = nil)
44
+ unless confirm == 'yes'
45
+ fail "Confirm delete project downloads feature with an argument of 'yes'"
46
+ else
47
+ # Flush cache
48
+ orig_downloads_name = downloads_name
49
+ @downloads_feature = @downloads_name = nil
50
+ @kc["projects/#{project}/features/#{orig_downloads_name}"].delete
51
+ end
52
+ end
53
+
54
+ #
55
+ # Returns the specified downloads feature if found, or a discovered one
56
+ # if the project only has one
57
+ #
58
+ def downloads_feature
59
+ @downloads_feature ||= begin
60
+ project = @kc.project(@project)
61
+ features = project && project['features']
62
+ if downloads_features = features && features.select { |f| f['type'] == 'downloads' }
63
+ if @specified_downloads_name
64
+ downloads_features.detect { |f| f['name'] == @specified_downloads_name }
65
+ elsif downloads_features.size == 1
66
+ downloads_features.first
67
+ else
68
+ nil
69
+ end
70
+ else
71
+ nil
72
+ end
73
+ end
74
+ end
75
+
76
+ def downloads_name
77
+ @downloads_name ||= downloads_feature && downloads_feature['name']
78
+ end
79
+
80
+ alias_method :ping, :downloads_name
81
+
82
+ def ls(path = '/')
83
+ entry(path)
84
+ end
85
+
86
+ def exist?(path = '/')
87
+ !!entry(path)
88
+ end
89
+
90
+ def entry_type(path)
91
+ if h = entry(path)
92
+ h['entry_type']
93
+ end
94
+ end
95
+
96
+ #
97
+ # Pull a remote file or directory hierarchy to the local host. Use
98
+ # +remote_path+ to specify the remote file or directory hierarchy to
99
+ # download. If +remote_path+ is '/', download all content. Use
100
+ # +local_dest_dir+ to specify the target location for the content which
101
+ # defaults to the current directory.
102
+ #
103
+ # For example:
104
+ # dlclient.pull('version-1.9')
105
+ # dlclient.pull('version-1.9', '/tmp/project_downloads')
106
+ #
107
+ def pull(remote_path, local_dest_dir = '.')
108
+ dest = Pathname(local_dest_dir)
109
+ fail "Destination must be a directory" unless dest.directory?
110
+ remote_pn = clean_id_pn(remote_path)
111
+ if ent = entry(remote_pn)
112
+ display_name = ent['display_name']
113
+ basename = display_name == '/' ? '.' : display_name
114
+ local_path = dest + basename
115
+ case ent['entry_type']
116
+ when 'directory'
117
+ local_path.mkdir unless local_path.exist?
118
+ ent['children'].each do |ch|
119
+ remote_child = remote_pn + ch['display_name']
120
+ pull(remote_child, local_path)
121
+ end
122
+ when 'file'
123
+ content = get_cloaked_url(ent['content_url'])
124
+ local_path.open("w") { |f| f.write(content) }
125
+ else
126
+ puts "Warning: skipping unsupported entry type"
127
+ end
128
+ else
129
+ puts "Unknown downloads entry: #{remote_pn}"
130
+ end
131
+ end
132
+
133
+ #
134
+ # Push a local file or directory hierarchy to the server. Use
135
+ # +local_path+ path to specify the local file or directory to upload. If
136
+ # +local_path+ is a directory that ends in '/', upload the contents of
137
+ # that directory instead of the directory and its contents. Use
138
+ # +remote_dir+ to specify the remote directory to upload to.
139
+ #
140
+ # For example:
141
+ # dlclient.push('version-1.9')
142
+ # dlclient.push('dist', '/version-1.9')
143
+ #
144
+ def push(local_path, remote_dir = '/', opts = {})
145
+ src = Pathname(local_path)
146
+ if src.directory?
147
+ if src.to_s.end_with?('/')
148
+ target_dir = remote_dir
149
+ else
150
+ target_dir = Pathname(remote_dir) + src.basename
151
+ mkdir(target_dir)
152
+ end
153
+ src.children.each do |ch|
154
+ push(ch, target_dir)
155
+ end
156
+ else
157
+ remote_id = Pathname(remote_dir) + src.basename
158
+ entry = {:content_data => File.new(src)}.merge(opts)
159
+ begin
160
+ @kc[entry_api_path(remote_id)].put(:entry => entry)
161
+ rescue => ex
162
+ err_msg = "Error: unable to upload to target '#{remote_id}'"
163
+ if server_msg = extract_error_message(ex)
164
+ err_msg += ": #{server_msg}"
165
+ end
166
+ $stderr.puts err_msg
167
+ raise ex
168
+ end
169
+ end
170
+ end
171
+
172
+ def mkdir(dir, opts = {})
173
+ path = Pathname(dir)
174
+ entry = {:display_name => path.basename}
175
+ entry.merge(opts)
176
+ @kc[entry_api_path(path)].put(:entry => entry)
177
+ end
178
+
179
+ def rm_r(path)
180
+ @kc[entry_api_path(path)].delete
181
+ end
182
+
183
+ def rm(path)
184
+ if entry_type(path) == 'directory'
185
+ fail "Entry is a directory: #{path}"
186
+ else
187
+ @kc[entry_api_path(path)].delete
188
+ end
189
+ end
190
+
191
+ def rmdir(path)
192
+ entry = entry(path)
193
+ if entry['entry_type'] == 'directory'
194
+ if entry['children'].size == 0
195
+ @kc[entry_api_path(path)].delete
196
+ else
197
+ fail "Directory not empty: #{path}"
198
+ end
199
+ else
200
+ fail "Not a directory: #{path}"
201
+ end
202
+ end
203
+
204
+ private
205
+
206
+ # Canonicalize an entry id and return a relative Pathname or root Pathname
207
+ def clean_id_pn(remote_path)
208
+ remote_pn = Pathname(remote_path)
209
+ root = Pathname('/')
210
+ if remote_pn.absolute? && remote_pn != root
211
+ remote_pn.relative_path_from(root)
212
+ else
213
+ remote_pn
214
+ end
215
+ end
216
+
217
+ def entry_api_path(id)
218
+ fail "Downloads feature not found" unless downloads_name
219
+ escaped_id = URI.escape(clean_id_pn(id).to_s)
220
+ "projects/#{@project}/features/#{downloads_name}/entries/#{escaped_id}"
221
+ end
222
+
223
+ def entry(path)
224
+ begin
225
+ resource = @kc[entry_api_path(path)].get
226
+ rescue RestClient::ResourceNotFound => err
227
+ return nil
228
+ end
229
+ JSON.parse(resource)
230
+ end
231
+
232
+ def get_cloaked_url(url)
233
+ opts = @cloak_password ? {:user => "dont_care", :password => @cloak_password} : {}
234
+ RestClient::Resource.new(url, opts).get
235
+ end
236
+
237
+ def extract_error_message(ex)
238
+ ex.response if ex.respond_to?(:response)
239
+ end
240
+ end
241
+ end