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