kenai_tools 0.0.7

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