jenkins2 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -22
- data/README.md +140 -76
- data/bin/jenkins2 +8 -1
- data/lib/jenkins2.rb +10 -5
- data/lib/jenkins2/api.rb +33 -0
- data/lib/jenkins2/api/computer.rb +49 -0
- data/lib/jenkins2/api/credentials.rb +151 -0
- data/lib/jenkins2/api/job.rb +50 -0
- data/lib/jenkins2/api/plugins.rb +55 -0
- data/lib/jenkins2/api/root.rb +34 -0
- data/lib/jenkins2/api/rud.rb +21 -0
- data/lib/jenkins2/api/user.rb +35 -0
- data/lib/jenkins2/api/view.rb +40 -0
- data/lib/jenkins2/cli.rb +157 -0
- data/lib/jenkins2/cli/credentials.rb +232 -0
- data/lib/jenkins2/cli/job.rb +48 -0
- data/lib/jenkins2/cli/nodes.rb +233 -0
- data/lib/jenkins2/cli/plugins.rb +101 -0
- data/lib/jenkins2/cli/root.rb +65 -0
- data/lib/jenkins2/cli/user.rb +16 -0
- data/lib/jenkins2/cli/view.rb +155 -0
- data/lib/jenkins2/connection.rb +90 -0
- data/lib/jenkins2/errors.rb +37 -0
- data/lib/jenkins2/log.rb +8 -7
- data/lib/jenkins2/resource_proxy.rb +46 -0
- data/lib/jenkins2/util.rb +67 -0
- data/lib/jenkins2/version.rb +3 -1
- metadata +33 -49
- data/lib/jenkins2/client.rb +0 -127
- data/lib/jenkins2/client/credential_commands.rb +0 -134
- data/lib/jenkins2/client/node_commands.rb +0 -105
- data/lib/jenkins2/client/plugin_commands.rb +0 -53
- data/lib/jenkins2/cmdparse.rb +0 -51
- data/lib/jenkins2/command_line.rb +0 -221
- data/lib/jenkins2/try.rb +0 -36
- data/lib/jenkins2/uri.rb +0 -10
- data/lib/jenkins2/wait.rb +0 -29
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open-uri'
|
4
|
+
|
5
|
+
module Jenkins2
|
6
|
+
class CLI
|
7
|
+
class InstallPlugin < CLI
|
8
|
+
def self.description
|
9
|
+
'Installs a plugin either from a file, an URL, standard input or from update center.'
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def add_options
|
15
|
+
parser.on '-n', '--name SHORTNAME', 'Plugin short name (like thinBackup).' do |n|
|
16
|
+
options[:name] = n
|
17
|
+
end
|
18
|
+
parser.on '--source URI', 'If this points to a local file, it will be '\
|
19
|
+
'installed. If this is an URL, the file will be downloaded and installed. If it '\
|
20
|
+
'the "-" sting, the file will be read from standard input and "--name" must be '\
|
21
|
+
'specified.' do |s|
|
22
|
+
options[:source] = s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def run
|
27
|
+
case options[:source]
|
28
|
+
when nil, ''
|
29
|
+
jc.plugins.install options[:name]
|
30
|
+
when '-'
|
31
|
+
jc.plugins.upload(ARGF.read, options[:name])
|
32
|
+
else
|
33
|
+
open(options[:source], 'rb') do |f|
|
34
|
+
jc.plugins.upload(f.read, options[:name] || File.basename(options[:source]))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class ListPlugins < CLI
|
41
|
+
def self.description
|
42
|
+
'Lists all installed plugins.'
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def run
|
48
|
+
jc.plugins(depth: 1).plugins.collect do |pl|
|
49
|
+
"#{pl.shortName} (#{pl.version})"
|
50
|
+
end.join("\n")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class UninstallPlugin < CLI
|
55
|
+
def self.description
|
56
|
+
'Uninstalls a plugin.'
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def add_options
|
62
|
+
parser.separator 'Mandatory arguments:'
|
63
|
+
parser.on '-n', '--name SHORTNAME', 'Plugin short name (like thinBackup).' do |n|
|
64
|
+
options[:name] = n
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def mandatory_arguments
|
69
|
+
super + [:name]
|
70
|
+
end
|
71
|
+
|
72
|
+
def run
|
73
|
+
jc.plugins.plugin(options[:name]).uninstall
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class ShowPlugin < CLI
|
78
|
+
def self.description
|
79
|
+
'Show plugin info.'
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def add_options
|
85
|
+
parser.separator 'Mandatory arguments:'
|
86
|
+
parser.on '-n', '--name SHORTNAME', 'Plugin short name (like thinBackup).' do |n|
|
87
|
+
options[:name] = n
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def mandatory_arguments
|
92
|
+
super + [:name]
|
93
|
+
end
|
94
|
+
|
95
|
+
def run
|
96
|
+
pl = jc.plugins.plugin(options[:name]).subject
|
97
|
+
"#{pl.shortName} (#{pl.version}) - #{pl.longName}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jenkins2
|
4
|
+
class CLI
|
5
|
+
class SafeRestart < CLI
|
6
|
+
def self.description
|
7
|
+
'Safely restart Jenkins.'
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def run
|
13
|
+
jc.restart
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Restart < CLI
|
18
|
+
def self.description
|
19
|
+
'Restart Jenkins.'
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def run
|
25
|
+
jc.restart!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class QuietDown < CLI
|
30
|
+
def self.description
|
31
|
+
'Put Jenkins into the quiet mode, wait for existing builds to be completed.'
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def run
|
37
|
+
jc.quiet_down
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class CancelQuietDown < CLI
|
42
|
+
def self.description
|
43
|
+
'Cancel previously issued quiet-down command.'
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def run
|
49
|
+
jc.cancel_quiet_down
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Version < CLI
|
54
|
+
def self.description
|
55
|
+
'Jenkins version.'
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def run
|
61
|
+
jc.version
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jenkins2
|
4
|
+
class CLI
|
5
|
+
class WhoAmI < CLI
|
6
|
+
def self.description
|
7
|
+
'Reports your credentials.'
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
r = jc.me.subject
|
12
|
+
%w[id fullName description].collect{|p| "#{p}: #{r[p]}" }.join "\n"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jenkins2
|
4
|
+
class CLI
|
5
|
+
class CreateView < CLI
|
6
|
+
def self.description
|
7
|
+
'Creates a new view by reading stdin as a XML configuration.'
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def add_options
|
13
|
+
parser.separator 'Mandatory arguments:'
|
14
|
+
parser.on '-n', '--name NAME', 'Name of the view.' do |n|
|
15
|
+
options[:name] = n
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def mandatory_arguments
|
20
|
+
super + [:name]
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
jc.view(options[:name]).create($stdin.read)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class DeleteView < CLI
|
29
|
+
def self.description
|
30
|
+
'Delete view(s).'
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def add_options
|
36
|
+
parser.separator 'Mandatory arguments:'
|
37
|
+
parser.on '-n', '--name X,Y,..', Array, 'View names to delete.' do |n|
|
38
|
+
options[:name] = n
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def mandatory_arguments
|
43
|
+
super + [:name]
|
44
|
+
end
|
45
|
+
|
46
|
+
def run
|
47
|
+
options[:name].all? do |name|
|
48
|
+
jc.view(name).delete
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class GetView < CLI
|
54
|
+
def self.description
|
55
|
+
'Dumps the view definition XML to stdout.'
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def add_options
|
61
|
+
parser.separator 'Mandatory arguments:'
|
62
|
+
parser.on '-n', '--name NAME', 'Name of the view.' do |n|
|
63
|
+
options[:name] = n
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def mandatory_arguments
|
68
|
+
super + [:name]
|
69
|
+
end
|
70
|
+
|
71
|
+
def run
|
72
|
+
jc.view(options[:name]).config_xml
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class UpdateView < CLI
|
77
|
+
def self.description
|
78
|
+
'Updates the view definition XML from stdin. The opposite of the get-view command.'
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def add_options
|
84
|
+
parser.separator 'Mandatory arguments:'
|
85
|
+
parser.on '-n', '--name NAME', 'Name of the view.' do |n|
|
86
|
+
options[:name] = n
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def mandatory_arguments
|
91
|
+
super + [:name]
|
92
|
+
end
|
93
|
+
|
94
|
+
def run
|
95
|
+
jc.view(options[:name]).update($stdin.read)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class AddJobToView < CLI
|
100
|
+
def self.description
|
101
|
+
'Adds jobs to view.'
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def add_options
|
107
|
+
parser.separator 'Mandatory arguments:'
|
108
|
+
parser.on '-n', '--name NAME', 'Name of the view.' do |n|
|
109
|
+
options[:name] = n
|
110
|
+
end
|
111
|
+
parser.on '-j', '--job X,Y,..', Array, 'Job name(s) to add.' do |j|
|
112
|
+
options[:job] = j
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def mandatory_arguments
|
117
|
+
super + %i[name job]
|
118
|
+
end
|
119
|
+
|
120
|
+
def run
|
121
|
+
options[:job].all? do |job|
|
122
|
+
jc.view(options[:name]).add_job(job)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class RemoveJobFromView < CLI
|
128
|
+
def self.description
|
129
|
+
'Removes jobs from view.'
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def add_options
|
135
|
+
parser.separator 'Mandatory arguments:'
|
136
|
+
parser.on '-n', '--name NAME', 'Name of the view.' do |n|
|
137
|
+
options[:name] = n
|
138
|
+
end
|
139
|
+
parser.on '-j', '--job X,Y,..', Array, 'Job name(s) to remove.' do |j|
|
140
|
+
options[:job] = j
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def mandatory_arguments
|
145
|
+
super + %i[name job]
|
146
|
+
end
|
147
|
+
|
148
|
+
def run
|
149
|
+
options[:job].all? do |job|
|
150
|
+
jc.view(options[:name]).remove_job(job)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'openssl'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Jenkins2
|
8
|
+
class Connection
|
9
|
+
# Creates a "connection" to Jenkins.
|
10
|
+
# Parameter:
|
11
|
+
# +server+:: Jenkins Server URL.
|
12
|
+
def initialize(url)
|
13
|
+
@server = url
|
14
|
+
@crumb = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add basic auth to existing connection. Returns self.
|
18
|
+
# Parameters:
|
19
|
+
# +user+:: Jenkins API user.
|
20
|
+
# +key+:: Jenkins API key.
|
21
|
+
def basic_auth(user, key)
|
22
|
+
@user, @key = user, key
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_json(path, params={}, &block)
|
27
|
+
get(::File.join(path, 'api/json'), params, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(path, params={}, &block)
|
31
|
+
api_request(Net::HTTP::Get, build_uri(path, params), &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def head(path, params={}, &block)
|
35
|
+
api_request(Net::HTTP::Head, build_uri(path, params), &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def post(path, body=nil, params={}, &block)
|
39
|
+
headers = { 'Content-Type' => 'application/x-www-form-urlencoded' }
|
40
|
+
api_request(Net::HTTP::Post, build_uri(path, params), body, headers, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_uri(relative_or_absolute, params={})
|
44
|
+
result = ::URI.parse relative_or_absolute
|
45
|
+
result = ::URI.parse ::File.join(@server.to_s, relative_or_absolute) unless result.absolute?
|
46
|
+
result.query = ::URI.encode_www_form params
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
def api_request(method, uri, body=nil, headers=nil)
|
51
|
+
req = method.new(URI(uri), headers)
|
52
|
+
req.basic_auth @user, @key if @user and @key
|
53
|
+
req.body = body
|
54
|
+
yield req if block_given?
|
55
|
+
Net::HTTP.start(req.uri.hostname, req.uri.port,
|
56
|
+
use_ssl: req.uri.scheme == 'https', verify_mode: OpenSSL::SSL::VERIFY_NONE) do |http|
|
57
|
+
begin
|
58
|
+
req[@crumb['crumbRequestField']] = @crumb['crumb'] if @crumb
|
59
|
+
Log.debug{ "Request uri: #{req.uri}" }
|
60
|
+
Log.debug{ "Request content_type: #{req.content_type}, body: #{req.body}" }
|
61
|
+
response = http.request req
|
62
|
+
handle_response response
|
63
|
+
rescue Jenkins2::NoValidCrumbMatcher
|
64
|
+
update_crumbs
|
65
|
+
retry
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def update_crumbs
|
71
|
+
@crumb = JSON.parse(get_json('/crumbIssuer').body)
|
72
|
+
end
|
73
|
+
|
74
|
+
def handle_response(response)
|
75
|
+
Log.debug{ "Response: #{response.code}, #{response.body}" }
|
76
|
+
case response
|
77
|
+
when Net::HTTPNotFound
|
78
|
+
raise Jenkins2::NotFoundError, response
|
79
|
+
when Net::HTTPBadRequest
|
80
|
+
raise Jenkins2::BadRequestError, response
|
81
|
+
when Net::HTTPServiceUnavailable
|
82
|
+
raise Jenkins2::ServiceUnavailableError, response
|
83
|
+
when Net::HTTPClientError, Net::HTTPServerError # 4XX, 5XX
|
84
|
+
response.value
|
85
|
+
else
|
86
|
+
response
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jenkins2
|
4
|
+
class BadRequestError < Net::HTTPError
|
5
|
+
def initialize(res)
|
6
|
+
if res.body.nil? or res.body.empty?
|
7
|
+
super('', res)
|
8
|
+
elsif (match = res.body.match('<h1>Error</h1><p>(.*)</p>'))
|
9
|
+
super(match[1], res)
|
10
|
+
elsif (match = res.body.match('<h2>HTTP ERROR 404</h2>\n<p>(.*) Reason:'))
|
11
|
+
super(match[1], res)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class NotFoundError < Net::HTTPError
|
17
|
+
def initialize(res)
|
18
|
+
super(res.body.match('<h2>HTTP ERROR 404</h2>\n<p>(.*) Reason:')[1], res)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class ServiceUnavailableError < Net::HTTPError
|
23
|
+
def initialize(res)
|
24
|
+
if res.body.nil? or res.body.empty?
|
25
|
+
super('', res)
|
26
|
+
else
|
27
|
+
super(res.body.match('<h1[^>]*>\n\s+([^<]+)')[1], res)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class NoValidCrumbMatcher
|
33
|
+
def self.===(exception)
|
34
|
+
exception.message == '403 "No valid crumb was included in the request"'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|