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.
- 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
|