jenkins2 0.1.0 → 1.0.0

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