jenkins2 0.1.0 → 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.
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rud'
4
+
5
+ module Jenkins2
6
+ class API
7
+ module Credentials
8
+ def credentials(params={})
9
+ Proxy.new connection, 'credentials', params
10
+ end
11
+
12
+ class Proxy < ::Jenkins2::ResourceProxy
13
+ def store(id, params={})
14
+ path = build_path 'store', id
15
+ Store::Proxy.new connection, path, params
16
+ end
17
+ end
18
+
19
+ module Store
20
+ class Proxy < ::Jenkins2::ResourceProxy
21
+ def domain(id, params={})
22
+ Domain::Proxy.new(connection, build_path('domain', id), params)
23
+ end
24
+
25
+ def create_domain(config_xml)
26
+ connection.post(build_path('createDomain'), config_xml) do |req|
27
+ req['Content-Type'] = 'text/xml'
28
+ end.code == '200'
29
+ end
30
+ end
31
+
32
+ module Domain
33
+ class Proxy < ::Jenkins2::ResourceProxy
34
+ include ::Jenkins2::API::RUD
35
+
36
+ BOUNDARY = '----Jenkins2RubyMultipartClient' + rand(1_000_000).to_s
37
+
38
+ # Creates ssh username with private key credentials. Jenkins must have ssh-credentials
39
+ # plugin installed, to use this functionality. Accepts the following key-word
40
+ # parameters.
41
+ # +scope+:: Scope of the credential. GLOBAL or SYSTEM
42
+ # +id+:: Id of the credential. Will be Generated by Jenkins, if not provided.
43
+ # +description+:: Human readable text, what this credential is used for.
44
+ # +username+:: Ssh username.
45
+ # +private_key+:: Ssh private key, with new lines replaced by <tt>\n</tt> sequence.
46
+ # +passphrase+:: Passphrase for the private key. Empty string, if not provided.
47
+ def create_ssh(private_key:, **args)
48
+ json_body = {
49
+ '' => '1',
50
+ credentials: args.merge(
51
+ privateKeySource: {
52
+ value: '0',
53
+ privateKey: private_key,
54
+ 'stapler-class' => 'com.cloudbees.jenkins.plugins.sshcredentials.impl.'\
55
+ 'BasicSSHUserPrivateKey$DirectEntryPrivateKeySource'
56
+ },
57
+ '$class' => 'com.cloudbees.jenkins.plugins.sshcredentials.impl.'\
58
+ 'BasicSSHUserPrivateKey'
59
+ )
60
+ }.to_json
61
+ create_("json=#{::CGI.escape json_body}")
62
+ end
63
+
64
+ # Creates a secret text credential. Jenkins must have plain-credentials plugin
65
+ # installed, to use this functionality. Accepts hash with the following parameters.
66
+ # +scope+:: Scope of the credential. GLOBAL or SYSTEM
67
+ # +id+:: Id of the credential. Will be Generated by Jenkins, if not provided.
68
+ # +description+:: Human readable text, what this credential is used for.
69
+ # +secret+:: Some secret text.
70
+ def create_secret_text(**args)
71
+ json_body = {
72
+ '' => '3',
73
+ credentials: args.merge(
74
+ '$class' => 'org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl'
75
+ )
76
+ }.to_json
77
+ create_("json=#{::CGI.escape json_body}")
78
+ end
79
+
80
+ # Creates a secret file credential. Jenkins must have plain-credentials plugin
81
+ # installed, to use this functionality. Accepts hash with the following parameters.
82
+ # +scope+:: Scope of the credential. GLOBAL or SYSTEM
83
+ # +id+:: Id of the credential. Will be Generated by Jenkins, if not provided.
84
+ # +description+:: Human readable text, what this credential is used for.
85
+ # +filename+:: Name of the file.
86
+ # +content+:: File content.
87
+ def create_secret_file(filename:, content:, **args)
88
+ body = "--#{BOUNDARY}\r\n"\
89
+ "Content-Disposition: form-data; name=\"file0\"; filename=\"#{filename}\"\r\n"\
90
+ "Content-Type: application/octet-stream\r\n\r\n"\
91
+ "#{content}\r\n"\
92
+ "--#{BOUNDARY}\r\n"\
93
+ "Content-Disposition: form-data; name=\"json\"\r\n\r\n"\
94
+ "#{{
95
+ '' => '2',
96
+ credentials: args.merge(
97
+ '$class' => 'org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl',
98
+ 'file' => 'file0'
99
+ )
100
+ }.to_json}"\
101
+ "\r\n\r\n--#{BOUNDARY}--\r\n"
102
+ create_(body, "multipart/form-data, boundary=#{BOUNDARY}")
103
+ end
104
+
105
+ # Creates username and password credential. Accepts hash with the following parameters.
106
+ # +scope+:: Scope of the credential. GLOBAL or SYSTEM
107
+ # +id+:: Id of the credential. Will be Generated by Jenkins, if not provided.
108
+ # +description+:: Human readable text, what this credential is used for.
109
+ # +username+:: Username.
110
+ # +password+:: Password in plain text.
111
+ def create_username_password(**args)
112
+ json_body = {
113
+ '' => '0',
114
+ credentials: args.merge(
115
+ '$class' => 'com.cloudbees.plugins.credentials.impl.'\
116
+ 'UsernamePasswordCredentialsImpl'
117
+ )
118
+ }.to_json
119
+ create_("json=#{::CGI.escape json_body}")
120
+ end
121
+
122
+ def create_(body, content_type='application/x-www-form-urlencoded')
123
+ connection.post(build_path('createCredentials'), body) do |req|
124
+ req['Content-Type'] = content_type
125
+ end.code == '302'
126
+ end
127
+
128
+ def create(config_xml)
129
+ connection.post(build_path('createCredentials'), config_xml) do |req|
130
+ req['Content-Type'] = 'text/xml'
131
+ end.code == '200'
132
+ end
133
+
134
+ # Returns credential as json. Raises Net::HTTPNotFound, if no such credential
135
+ # +id+:: Credential's id
136
+ def credential(id, params={})
137
+ path = build_path 'credential', id
138
+ Credential::Proxy.new connection, path, params
139
+ end
140
+ end
141
+
142
+ module Credential
143
+ class Proxy < ::Jenkins2::ResourceProxy
144
+ include ::Jenkins2::API::RUD
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rud'
4
+
5
+ module Jenkins2
6
+ class API
7
+ module Job
8
+ def job(name, **params)
9
+ proxy = Proxy.new connection, 'job', params
10
+ proxy.id = name
11
+ proxy
12
+ end
13
+
14
+ class Proxy < ::Jenkins2::ResourceProxy
15
+ attr_accessor :id
16
+ include ::Jenkins2::API::RUD
17
+
18
+ def create(config_xml)
19
+ connection.post('createItem', config_xml, name: id) do |req|
20
+ req['Content-Type'] = 'text/xml'
21
+ end.code == '200'
22
+ end
23
+
24
+ def copy(from)
25
+ connection.post('createItem', nil, name: id, from: from, mode: 'copy').code == '302'
26
+ end
27
+
28
+ def disable
29
+ connection.post(build_path('disable')).code == '302'
30
+ end
31
+
32
+ def enable
33
+ connection.post(build_path('enable')).code == '302'
34
+ end
35
+
36
+ def build(build_parameters={})
37
+ if build_parameters.empty?
38
+ connection.post(build_path('build'))
39
+ else
40
+ connection.post(build_path('buildWithParameters'), nil, build_parameters)
41
+ end.code == '201'
42
+ end
43
+
44
+ def polling
45
+ connection.post(build_path('polling')).code == '302'
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jenkins2
4
+ class API
5
+ module Plugins
6
+ def plugins(**params)
7
+ Proxy.new connection, 'pluginManager', params
8
+ end
9
+
10
+ class Proxy < ::Jenkins2::ResourceProxy
11
+ BOUNDARY = '----Jenkins2RubyMultipartClient' + rand(1_000_000).to_s
12
+
13
+ def install(*short_names)
14
+ path = build_path 'install'
15
+ form_data = short_names.flatten.inject({}) do |memo, obj|
16
+ memo.merge "plugin.#{obj}.default" => 'on'
17
+ end.merge('dynamicLoad' => 'Install without restart')
18
+ connection.post(path, ::URI.encode_www_form(form_data)).code == '302'
19
+ end
20
+
21
+ def upload(hpi_file, filename)
22
+ body = "--#{BOUNDARY}\r\n"\
23
+ "Content-Disposition: form-data; name=\"file0\"; filename=\"#{filename}\"\r\n"\
24
+ "Content-Type: application/octet-stream\r\n\r\n"\
25
+ "#{hpi_file}\r\n"\
26
+ "--#{BOUNDARY}\r\n"\
27
+ "Content-Disposition: form-data; name=\"json\"\r\n\r\n"\
28
+ "\r\n\r\n--#{BOUNDARY}--\r\n"
29
+ connection.post(build_path('uploadPlugin'), body) do |req|
30
+ req['Content-Type'] = "multipart/form-data, boundary=#{BOUNDARY}"
31
+ end.code == '302'
32
+ end
33
+
34
+ def plugin(id, params={})
35
+ path = build_path 'plugin', id
36
+ Plugin::Proxy.new connection, path, params
37
+ end
38
+ end
39
+
40
+ module Plugin
41
+ class Proxy < ::Jenkins2::ResourceProxy
42
+ def uninstall
43
+ path = build_path 'doUninstall'
44
+ form_data = { 'Submit' => 'Yes', 'json' => '{}' }
45
+ connection.post(path, ::URI.encode_www_form(form_data)).code == '302'
46
+ end
47
+
48
+ def active?
49
+ raw.instance_of? ::Net::HTTPOK and subject.active
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jenkins2
4
+ class API
5
+ module Root
6
+ def root(**params)
7
+ Proxy.new connection, '', params
8
+ end
9
+
10
+ def version
11
+ connection.head('/')['X-Jenkins']
12
+ end
13
+
14
+ def quiet_down
15
+ connection.post('quietDown').code == '302'
16
+ end
17
+
18
+ def cancel_quiet_down
19
+ connection.post('cancelQuietDown').code == '302'
20
+ end
21
+
22
+ def restart
23
+ connection.post('safeRestart').code == '302'
24
+ end
25
+
26
+ def restart!
27
+ connection.post('restart').code == '302'
28
+ end
29
+
30
+ class Proxy < ::Jenkins2::ResourceProxy
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jenkins2
4
+ class API
5
+ module RUD
6
+ # = Read
7
+ def config_xml
8
+ connection.get(build_path('config.xml')).body
9
+ end
10
+
11
+ def update(config_xml)
12
+ connection.post(build_path('config.xml'), config_xml).code == '200'
13
+ end
14
+
15
+ def delete
16
+ connection.post(build_path('doDelete')).code == '302'
17
+ # connection.delete(build_path('config.xml')).code == '302'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jenkins2
4
+ class API
5
+ module User
6
+ def me(**params)
7
+ Me::Proxy.new connection, 'me', params
8
+ end
9
+
10
+ def user(id, **params)
11
+ proxy = Proxy.new connection, 'user', params
12
+ proxy.id = id
13
+ proxy
14
+ end
15
+
16
+ def people(**params)
17
+ People::Proxy.new connection, 'people', params
18
+ end
19
+
20
+ class Proxy < ::Jenkins2::ResourceProxy
21
+ attr_accessor :id
22
+ end
23
+
24
+ module Me
25
+ class Proxy < ::Jenkins2::ResourceProxy
26
+ end
27
+ end
28
+
29
+ module People
30
+ class Proxy < ::Jenkins2::ResourceProxy
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rud'
4
+
5
+ module Jenkins2
6
+ class API
7
+ module View
8
+ MODE_LIST_VIEW = 'hudson.model.ListView'
9
+
10
+ def view(id, **params)
11
+ proxy = Proxy.new connection, 'view', params
12
+ proxy.id = id
13
+ proxy
14
+ end
15
+
16
+ def views(**params)
17
+ ::Jenkins2::ResourceProxy.new(connection, '', params).views
18
+ end
19
+
20
+ class Proxy < ::Jenkins2::ResourceProxy
21
+ attr_accessor :id
22
+ include ::Jenkins2::API::RUD
23
+
24
+ def create(config_xml)
25
+ connection.post('createView', config_xml, name: id) do |req|
26
+ req['Content-Type'] = 'text/xml'
27
+ end.code == '200'
28
+ end
29
+
30
+ def add_job(job_name)
31
+ connection.post(build_path('addJobToView'), nil, name: job_name).code == '200'
32
+ end
33
+
34
+ def remove_job(job_name)
35
+ connection.post(build_path('removeJobFromView'), nil, name: job_name).code == '200'
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse/uri'
4
+ require 'yaml'
5
+
6
+ require_relative 'version'
7
+ require_relative 'cli/credentials'
8
+ require_relative 'cli/nodes'
9
+ require_relative 'cli/root'
10
+ require_relative 'cli/plugins'
11
+ require_relative 'cli/user'
12
+ require_relative 'cli/view'
13
+
14
+ module Jenkins2
15
+ class CLI
16
+ attr_reader :options, :errors
17
+
18
+ def initialize(options={})
19
+ @options = options.dup
20
+ @command = []
21
+ @errors = []
22
+ @parser = nil
23
+ add_options
24
+ end
25
+
26
+ def call
27
+ if options[:help]
28
+ summary
29
+ elsif options[:version]
30
+ Jenkins2::VERSION
31
+ elsif !errors.empty?
32
+ errors.join("\n") + "\n" + summary
33
+ else
34
+ run
35
+ end
36
+ end
37
+
38
+ def parse(args)
39
+ parser.order! args do |nonopt|
40
+ @command << nonopt
41
+ return command_to_class.new(options).parse(args) if command_to_class
42
+ next
43
+ end
44
+ missing = mandatory_arguments.select{|a| options[a].nil? }
45
+ @errors << "Missing argument(s): #{missing.join(', ')}." unless missing.empty?
46
+ self
47
+ end
48
+
49
+ # This method should be overwritten in subclasses
50
+ def self.description; end
51
+
52
+ def self.class_to_command
53
+ to_s.split('::').last.gsub(/(.)([A-Z])/, '\1-\2').downcase if superclass == Jenkins2::CLI
54
+ end
55
+
56
+ def self.subcommands
57
+ constants(false).collect{|c| const_get(c) }.select do |c|
58
+ c.is_a?(Class) and c.superclass == Jenkins2::CLI
59
+ end.sort_by(&:to_s)
60
+ end
61
+
62
+ private
63
+
64
+ # This method can be overwritten in subclasses, to add more mandatory arguments
65
+ def mandatory_arguments
66
+ [:server]
67
+ end
68
+
69
+ # This method should be overwritten in subclasses
70
+ def add_options; end
71
+
72
+ # This method should be overwritten in subclasses
73
+ def run
74
+ summary
75
+ end
76
+
77
+ def summary
78
+ if self.class.subcommands.empty?
79
+ global_parser.to_s + parser.to_s
80
+ else
81
+ parser.separator 'Commands:'
82
+ self.class.subcommands.each do |sc|
83
+ key = sc.class_to_command
84
+ parser.base.append(OptionParser::Switch::NoArgument.new(key, nil, [key], nil, nil,
85
+ [sc.description], proc{ OptionParser.new(&block) }), [], [key])
86
+ end
87
+ parser.to_s
88
+ end
89
+ end
90
+
91
+ def parser
92
+ @parser ||= if (key = self.class.class_to_command)
93
+ OptionParser.new do |parser|
94
+ parser.banner = 'Command:'
95
+ parser.top.append(OptionParser::Switch::NoArgument.new(key, nil, [key], nil,
96
+ nil, [self.class.description], proc{ OptionParser.new(&block) }), [], [key])
97
+ end
98
+ else
99
+ global_parser
100
+ end
101
+ end
102
+
103
+ def global_parser
104
+ @global_parser ||= OptionParser.new do |parser|
105
+ parser.banner = 'Usage: jenkins2 [global-arguments] <command> [command-arguments]'
106
+ parser.separator ''
107
+ parser.separator 'Global arguments (accepted by all commands):'
108
+ parser.on '-s', '--server URL', ::URI, 'Jenkins Server Url' do |s|
109
+ @options[:server] = s
110
+ end
111
+ parser.on '-u', '--user USER', 'Jenkins API User' do |u|
112
+ @options[:user] = u
113
+ end
114
+ parser.on '-k', '--key KEY', 'Jenkins API Key' do |k|
115
+ @options[:key] = k
116
+ end
117
+ parser.on '-c', '--config [PATH]', %(Read configuration file. All global options can be \
118
+ configured in configuration file. File format is yaml. Arguments provided in command line will \
119
+ overwrite those in configuration file. Program looks for .jenkins2.conf in current directory if \
120
+ no PATH is provided.) do |c|
121
+ @options[:config] = c || ::File.join('.jenkins2.conf')
122
+ config_file_options = YAML.load_file(options[:config])
123
+ @options = config_file_options.merge options
124
+ end
125
+ parser.on '-l', '--log FILE', 'Log file. Prints to standard out, if not provided' do |l|
126
+ @options[:log] = l
127
+ end
128
+ parser.on '-v', '--verbose VALUE', Integer, 'Print more info. 1 up to 3. Prints only '\
129
+ 'errors by default.' do |v|
130
+ @options[:verbose] = v
131
+ end
132
+ parser.on '-h', '--help', 'Show help' do
133
+ @options[:help] = true
134
+ end
135
+ parser.on '-V', '--version', 'Show version' do
136
+ @options[:version] = true
137
+ end
138
+ parser.separator ''
139
+ parser.separator 'For command specific arguments run: jenkins2 --help <command>'
140
+ parser.separator ''
141
+ end
142
+ end
143
+
144
+ def command_to_class
145
+ const = @command.join('-').split('-').map(&:capitalize).join
146
+ if self.class.const_defined?(const)
147
+ klass = self.class.const_get(const)
148
+ return klass if klass.is_a?(Class) and klass.superclass == Jenkins2::CLI
149
+ end
150
+ nil
151
+ end
152
+
153
+ def jc
154
+ @jc ||= Jenkins2.connect options
155
+ end
156
+ end
157
+ end