Skip to content

Commit 467b3db

Browse files
committed
Add support for users, roles and npm
1 parent c6371ce commit 467b3db

File tree

5 files changed

+267
-13
lines changed

5 files changed

+267
-13
lines changed

cookbooks/boxcutter_sonatype/attributes/default.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@
2020
# '-Dkaraf.log' => '/opt/sonatype-work/nexus3/log ',
2121
# }
2222
},
23-
'properties' => {
24-
},
23+
'properties' => {},
24+
'repositories' => {},
25+
'roles' => {},
26+
'users' => {},
2527
'blobstores' => {
2628
'default' => {
2729
'name' => 'default',
2830
'type' => 'file',
2931
'path' => 'default',
3032
},
3133
},
32-
'repositories' => {},
3334
}

cookbooks/boxcutter_sonatype/libraries/default.rb

Lines changed: 192 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,157 @@ def self.set_realms_active(node, realms)
6464
end
6565
end
6666

67+
# curl -u admin:password \
68+
# -H "accept: application/json" \
69+
# -X GET 'http://127.0.0.1:8081/service/rest/v1/security/realms/available'
70+
def self.get_realms_available(node)
71+
uri = URI.parse('http://localhost:8081/service/rest/v1/security/realms/available')
72+
request = Net::HTTP::Get.new(uri)
73+
request.basic_auth(admin_username(node), admin_password(node))
74+
request['Accept'] = 'application/json'
75+
76+
response = Net::HTTP.start(uri.hostname, uri.port) do |http|
77+
http.request(request)
78+
end
79+
80+
if response.is_a?(Net::HTTPSuccess)
81+
available_realms = JSON.parse(response.body)
82+
Chef::Log.info("Available Realms: #{available_realms}")
83+
else
84+
Chef::Log.error("Failed to query available realms. HTTP Status: #{response.code}")
85+
end
86+
available_realms
87+
end
88+
6789
# curl -u admin:Superseekret63 \
6890
# -H "Content-Type: application/json" \
6991
# -X GET "http://localhost:8081/service/rest/v1/security/roles"
70-
def self.list_roles; end
92+
def self.roles_list(node)
93+
uri = URI.parse('http://localhost:8081/service/rest/v1/security/roles')
94+
request = Net::HTTP::Get.new(uri)
95+
request.basic_auth(admin_username(node), admin_password(node))
96+
request['Accept'] = 'application/json'
7197

72-
def self.create_role; end
98+
response = Net::HTTP.start(uri.hostname, uri.port) do |http|
99+
http.request(request)
100+
end
101+
102+
if response.code.to_i == 200
103+
roles = JSON.parse(response.body)
104+
Chef::Log.info("Roles: #{roles}")
105+
# You can process the repositories as needed here
106+
else
107+
Chef::Log.error("Failed to get roles: #{response.message}")
108+
end
109+
roles
110+
end
111+
112+
def self.role_create_payload(role_id, role_config)
113+
{
114+
'id' => role_id,
115+
'name' => role_config['name'],
116+
'description' => role_config['description'],
117+
'privileges' => role_config['privileges'],
118+
'roles' => role_config['roles'],
119+
}.to_json
120+
end
121+
122+
def self.role_create(node, role_id, role_config)
123+
uri = URI.parse('http://localhost:8081/service/rest/v1/security/roles')
124+
125+
payload = role_create_payload(role_id, role_config)
126+
puts "MISCHA: role_create_payload: #{payload}"
127+
128+
http = Net::HTTP.new(uri.host, uri.port)
129+
130+
request = Net::HTTP::Post.new(uri.request_uri, { 'Content-Type' => 'application/json' })
131+
request.basic_auth(admin_username(node), admin_password(node))
132+
request.body = payload
133+
134+
response = http.request(request)
135+
136+
puts "MISCHA: PUT response code: #{response.code}"
137+
puts "MISCHA: PUT response body: #{response.body}"
138+
end
139+
140+
def self.role_delete(node, role_id)
141+
uri = URI.parse("http://localhost:8081/service/rest/v1/security/roles/#{role_id}")
142+
http = Net::HTTP.new(uri.hostname, uri.port)
143+
144+
request = Net::HTTP::Delete.new(uri)
145+
request.basic_auth(admin_username(node), admin_password(node))
146+
147+
response = http.request(request)
148+
149+
puts "MISCHA: DELETE response code: #{response.code}"
150+
puts "MISCHA: DELETE response body: #{response.body}"
151+
end
73152

74153
# curl -u admin:password \
75154
# -H "Content-Type: application/json" \
76155
# -X GET "http://localhost:8081/service/rest/v1/security/users"
77-
def self.list_users; end
156+
def self.users_list(node)
157+
uri = URI.parse('http://localhost:8081/service/rest/v1/security/users')
158+
request = Net::HTTP::Get.new(uri)
159+
request.basic_auth(admin_username(node), admin_password(node))
160+
request['Accept'] = 'application/json'
161+
162+
response = Net::HTTP.start(uri.hostname, uri.port) do |http|
163+
http.request(request)
164+
end
165+
166+
if response.code.to_i == 200
167+
users = JSON.parse(response.body)
168+
Chef::Log.info("Users: #{users}")
169+
# You can process the repositories as needed here
170+
else
171+
Chef::Log.error("Failed to get repositories: #{response.message}")
172+
end
173+
users
174+
end
175+
176+
def self.user_create_payload(user_id, user_config)
177+
{
178+
'userId' => user_id,
179+
'firstName' => user_config['first_name'],
180+
'lastName' => user_config['last_name'],
181+
'emailAddress' => user_config['email_address'],
182+
'password' => user_config['password'],
183+
'status' => 'active',
184+
'roles' => user_config['roles'],
185+
}.to_json
186+
end
187+
188+
def self.user_create(node, user_id, user_config)
189+
uri = URI.parse('http://localhost:8081/service/rest/v1/security/users')
78190

79-
def self.create_user; end
191+
payload = user_create_payload(user_id, user_config)
192+
puts "MISCHA: user_create_payload: #{payload}"
193+
194+
http = Net::HTTP.new(uri.host, uri.port)
195+
196+
request = Net::HTTP::Post.new(uri.request_uri, { 'Content-Type' => 'application/json' })
197+
request.basic_auth(admin_username(node), admin_password(node))
198+
request.body = payload
199+
200+
response = http.request(request)
201+
202+
puts "MISCHA: PUT response code: #{response.code}"
203+
puts "MISCHA: PUT response body: #{response.body}"
204+
end
205+
206+
def self.user_delete(node, user_id)
207+
uri = URI.parse("http://localhost:8081/service/rest/v1/security/users/#{user_id}")
208+
http = Net::HTTP.new(uri.hostname, uri.port)
209+
210+
request = Net::HTTP::Delete.new(uri)
211+
request.basic_auth(admin_username(node), admin_password(node))
212+
213+
response = http.request(request)
214+
215+
puts "MISCHA: DELETE response code: #{response.code}"
216+
puts "MISCHA: DELETE response body: #{response.body}"
217+
end
80218

81219
def self.change_user_password(user_id, new_password); end
82220

@@ -265,6 +403,54 @@ def self.repository_create_docker_payload(repository_name, repository_config)
265403
end
266404
end
267405

406+
def self.repository_create_npm_payload(repository_name, repository_config)
407+
case repository_config['type']
408+
when 'hosted'
409+
{
410+
'name' => repository_name,
411+
'online' => true,
412+
'storage' => {
413+
'blobStoreName' => repository_config.fetch('storage_blob_store_name', 'default'),
414+
'strictContentTypeValidation' => true,
415+
'writePolicy' => 'allow',
416+
},
417+
'cleanup' => {
418+
'policyNames' => [],
419+
},
420+
}.to_json
421+
when 'proxy'
422+
{
423+
'name' => repository_name,
424+
'online' => true,
425+
'storage' => {
426+
'blobStoreName' => repository_config.fetch('storage_blob_store_name', 'default'),
427+
'strictContentTypeValidation' => false,
428+
},
429+
'proxy' => {
430+
'remoteUrl' => repository_config['remote_url'],
431+
'contentMaxAge' => 1440,
432+
'metadataMaxAge' => 1440,
433+
},
434+
'negativeCache' => {
435+
'enabled' => true,
436+
'timeToLive' => 1440,
437+
},
438+
'httpClient' => {
439+
'blocked' => false,
440+
'autoBlock' => true,
441+
'connection' => {
442+
'retries' => 0,
443+
# 'userAgentSuffix' => 'string',
444+
'timeout' => 60,
445+
'enableCircularRedirects' => false,
446+
'enableCookies' => false,
447+
'useTrustStore' => false,
448+
},
449+
},
450+
}.to_json
451+
end
452+
end
453+
268454
def self.repository_create_pypi_payload(repository_name, repository_config)
269455
case repository_config['type']
270456
when 'hosted'
@@ -374,6 +560,8 @@ def self.repository_create(node, repository_name, repository_config)
374560
payload = repository_create_apt_payload(repository_name, repository_config)
375561
when 'docker'
376562
payload = repository_create_docker_payload(repository_name, repository_config)
563+
when 'npm'
564+
payload = repository_create_npm_payload(repository_name, repository_config)
377565
when 'pypi'
378566
payload = repository_create_pypi_payload(repository_name, repository_config)
379567
when 'raw'

cookbooks/boxcutter_sonatype/recipes/default.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@
4343
end
4444

4545
# https://help.sonatype.com/en/download.html
46-
version = 'nexus-3.76.0-03'
47-
url = 'https://download.sonatype.com/nexus/3/nexus-3.76.0-03-unix.tar.gz'
48-
checksum = 'd336a1c1fa3c26ee977ef720707d7bbca660aee5bf7369a9037293910c63c672'
46+
version = 'nexus-3.76.1-01'
47+
url = 'https://download.sonatype.com/nexus/3/nexus-3.76.1-01-unix.tar.gz'
48+
checksum = 'e6a68b903a445fc6b923a2ea922accb336e659a838099f2efb08e382332ff8f1'
4949

5050
tmp_path = ::File.join(Chef::Config[:file_cache_path], ::File.basename(url))
5151

cookbooks/boxcutter_sonatype/resources/boxcutter_sonatype_nexus_repository.rb

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,35 @@ class Helpers
1313
Boxcutter::Sonatype::Helpers.set_realms_active(node, desired_realms)
1414
end
1515

16-
# puts "MISCHA: list repositories=#{properties}"
17-
# node['boxcutter_sonatype']['nexus_repository']['repositories'] each do |repository_name, repository_info|
18-
# end
16+
puts "MISCHA: list roles=#{Boxcutter::Sonatype::Helpers.roles_list(node)}"
17+
current_role_names = Boxcutter::Sonatype::Helpers.roles_list(node).map { |role| role['id'] }
18+
puts "MISCHA: current_role_names=#{current_role_names}"
19+
filtered_current_role_names = current_role_names.reject do |user_id|
20+
['nx-admin', 'nx-anonymous'].include?(user_id)
21+
end
22+
puts "MISCHA: filtered_current_user_names=#{filtered_current_role_names}"
23+
desired_roles = node['boxcutter_sonatype']['nexus_repository']['roles']
24+
desired_role_names = desired_roles.map { |key, role| role['id'] || key }
25+
puts "MISCHA: desired_role_names=#{desired_role_names}"
26+
roles_to_delete = filtered_current_role_names - desired_role_names
27+
roles_to_delete.each do |role_name|
28+
Boxcutter::Sonatype::Helpers.role_delete(node, role_name)
29+
end
30+
31+
puts "MISCHA: list users=#{Boxcutter::Sonatype::Helpers.users_list(node)}"
32+
current_user_names = Boxcutter::Sonatype::Helpers.users_list(node).map { |user| user['userId'] }
33+
puts "MISCHA: current_user_names=#{current_user_names}"
34+
filtered_current_user_names = current_user_names.reject do |user_id|
35+
['anonymous', 'admin'].include?(user_id)
36+
end
37+
puts "MISCHA: filtered_current_user_names=#{filtered_current_user_names}"
38+
desired_users = node['boxcutter_sonatype']['nexus_repository']['users']
39+
desired_user_names = desired_users.map { |key, user| user['user_id'] || key }
40+
puts "MISCHA: desired_user_names=#{desired_user_names}"
41+
users_to_delete = filtered_current_user_names - desired_user_names
42+
users_to_delete.each do |user_name|
43+
Boxcutter::Sonatype::Helpers.user_delete(node, user_name)
44+
end
1945

2046
puts "MISCHA: list blobstores=#{Boxcutter::Sonatype::Helpers.blobstores_list(node)}"
2147
current_blobstore_names = Boxcutter::Sonatype::Helpers.blobstores_list(node).map { |blobstore| blobstore['name'] }
@@ -28,6 +54,10 @@ class Helpers
2854
Boxcutter::Sonatype::Helpers.blobstore_delete(node, blobstore_name)
2955
end
3056

57+
# puts "MISCHA: list repositories=#{properties}"
58+
# node['boxcutter_sonatype']['nexus_repository']['repositories'] each do |repository_name, repository_info|
59+
# end
60+
3161
puts "MISCHA: list repositories=#{Boxcutter::Sonatype::Helpers.repositories_list(node)}"
3262
current_repository_names = Boxcutter::Sonatype::Helpers.repositories_list(node).map { |repo| repo['name'] }
3363
puts "MISCHA: current_repository_names=#{current_repository_names}"
@@ -39,6 +69,16 @@ class Helpers
3969
Boxcutter::Sonatype::Helpers.repository_delete(node, repository_name)
4070
end
4171

72+
node['boxcutter_sonatype']['nexus_repository']['roles'].each do |role_id, role_config|
73+
next if filtered_current_role_names.include?(role_id)
74+
Boxcutter::Sonatype::Helpers.role_create(node, role_id, role_config)
75+
end
76+
77+
node['boxcutter_sonatype']['nexus_repository']['users'].each do |user_name, user_config|
78+
next if filtered_current_user_names.include?(user_name)
79+
Boxcutter::Sonatype::Helpers.user_create(node, user_name, user_config)
80+
end
81+
4282
node['boxcutter_sonatype']['nexus_repository']['blobstores'].each do |blobstore_name, blobstore_config|
4383
next if current_blobstore_names.include?(blobstore_name)
4484
Boxcutter::Sonatype::Helpers.blobstore_create(node, blobstore_name, blobstore_config)

cookbooks/boxcutter_sonatype/test/cookbooks/boxcutter_sonatype_test/recipes/default.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,31 @@
99
# node.run_state['boxcutter_sonatype']['nexus_repository']['admin_username'] = 'chef'
1010
# node.run_state['boxcutter_sonatype']['nexus_repository']['admin_password'] = 'sucre-canonize-ROADSTER-bashful'
1111

12+
node.default['boxcutter_sonatype']['nexus_repository']['roles'] = {
13+
'engineering-read-only' => {
14+
'id' => 'engineering-read-only',
15+
'name' => 'engineering-read-only',
16+
'description' => 'Read-only access to engineering repositories',
17+
'privileges' => [
18+
'nx-healthcheck-read',
19+
'nx-search-read',
20+
'nx-repository-view-*-*-read',
21+
'nx-repository-view-*-*-browse',
22+
],
23+
'roles' => [],
24+
},
25+
}
26+
node.default['boxcutter_sonatype']['nexus_repository']['users'] = {
27+
'chef' => {
28+
'user_id' => 'chef',
29+
'first_name' => 'Chef',
30+
'last_name' => 'User',
31+
'email_address' => 'nobody@nowhere.com',
32+
'password' => 'superseekret',
33+
'roles' => ['nx-admin'],
34+
},
35+
}
36+
1237
node.default['boxcutter_sonatype']['nexus_repository']['blobstores'] = {
1338
'default' => {
1439
'name' => 'default',

0 commit comments

Comments
 (0)