ood_core 0.31.0 → 0.31.1
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a2dab30b28f29c33b19e8cce3b5464805c09ba374336ac68fa27da684826fca6
|
|
4
|
+
data.tar.gz: f94e04cb03fa816f4fa54f72e43acdfac2e5a3ee301bd0de59624ed4367669f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 627ea1414e51fe18601977b50ad1b6472e58904a4ec6571620bc4a7dfa382c952e6aee65e04243352e4ed7ef842365a559134c2155d27c47a77fda819385afe0
|
|
7
|
+
data.tar.gz: 535c8466bf1e18192b850ab6c0c116c7b36173923c918c9bccc23d4e751acc9178c7f16a189efa518e103ec57be2b72e6d68d8ab2d11ec55a5874a525fac0ac5
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
require "ood_core/refinements/hash_extensions"
|
|
2
2
|
require "json"
|
|
3
|
-
|
|
4
3
|
|
|
5
4
|
# Utility class for the Coder adapter to interact with the Coders API.
|
|
6
5
|
class OodCore::Job::Adapters::Coder::Batch
|
|
@@ -10,8 +9,8 @@ class OodCore::Job::Adapters::Coder::Batch
|
|
|
10
9
|
@host = config[:host]
|
|
11
10
|
@token = config[:token]
|
|
12
11
|
@service_user = config[:service_user]
|
|
13
|
-
@
|
|
14
|
-
@
|
|
12
|
+
@deletion_max_attempts = config[:deletion_max_attempts] || 5
|
|
13
|
+
@deletion_timeout_interval_seconds = config[:deletion_timeout_interval] || 10
|
|
15
14
|
@credentials = credentials
|
|
16
15
|
end
|
|
17
16
|
|
|
@@ -38,63 +37,115 @@ class OodCore::Job::Adapters::Coder::Batch
|
|
|
38
37
|
}
|
|
39
38
|
end
|
|
40
39
|
|
|
40
|
+
def generate_coder_workspace_name(submitted_name)
|
|
41
|
+
"#{username}-#{submitted_name}-#{rand(2_821_109_907_456).to_s(36)}"
|
|
42
|
+
end
|
|
41
43
|
def submit(script)
|
|
42
|
-
org_id = script.native[:org_id]
|
|
43
44
|
project_id = script.native[:project_id]
|
|
44
|
-
|
|
45
|
+
app_credentials = @credentials.generate_credentials(project_id)
|
|
46
|
+
workspace_name = generate_coder_workspace_name(script.native[:workspace_name])
|
|
47
|
+
|
|
48
|
+
create_coder_workspace(
|
|
49
|
+
script.native[:org_id],
|
|
50
|
+
project_id,
|
|
51
|
+
script.native[:template_version_id],
|
|
52
|
+
script.native[:coder_parameters],
|
|
53
|
+
app_credentials,
|
|
54
|
+
workspace_name)
|
|
55
|
+
|
|
56
|
+
@credentials.save_credentials(workspace_name, app_credentials)
|
|
57
|
+
workspace_name
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def create_coder_workspace(org_id, project_id, template_version_id, coder_parameters, app_credentials, name)
|
|
45
61
|
endpoint = "#{@host}/api/v2/organizations/#{org_id}/members/#{@service_user}/workspaces"
|
|
46
|
-
app_credentials = @credentials.generate_credentials(project_id, username)
|
|
47
62
|
headers = get_headers(@token)
|
|
48
|
-
workspace_name = "#{username}-#{script.native[:workspace_name]}-#{rand(2_821_109_907_456).to_s(36)}"
|
|
49
63
|
body = {
|
|
50
|
-
template_version_id:
|
|
51
|
-
name:
|
|
64
|
+
template_version_id: template_version_id,
|
|
65
|
+
name: name,
|
|
52
66
|
rich_parameter_values: get_rich_parameters(coder_parameters, project_id, app_credentials),
|
|
53
67
|
}
|
|
68
|
+
api_call('post', endpoint, headers, body)
|
|
69
|
+
end
|
|
54
70
|
|
|
55
|
-
resp = api_call('post', endpoint, headers, body)
|
|
56
|
-
@credentials.save_credentials(resp["id"], username, app_credentials)
|
|
57
|
-
resp["id"]
|
|
58
71
|
|
|
59
|
-
|
|
72
|
+
def delete_coder_workspace(id)
|
|
73
|
+
build_id = get_workspace_info(id)["id"]
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
endpoint = "#{@host}/api/v2/workspaces/#{id}/builds"
|
|
75
|
+
endpoint = "#{@host}/api/v2/workspaces/#{build_id}/builds"
|
|
63
76
|
headers = get_headers(@token)
|
|
64
77
|
body = {
|
|
65
78
|
'orphan' => false,
|
|
66
79
|
'transition' => 'delete'
|
|
67
80
|
}
|
|
68
81
|
api_call('post', endpoint, headers, body)
|
|
82
|
+
end
|
|
69
83
|
|
|
70
|
-
|
|
71
|
-
|
|
84
|
+
def delete(id)
|
|
85
|
+
delete_coder_workspace(id)
|
|
86
|
+
|
|
87
|
+
credentials = @credentials.load_credentials(id)
|
|
88
|
+
puts "credentials loaded #{credentials["id"]}"
|
|
72
89
|
wait_for_workspace_deletion(id) do |attempt|
|
|
73
|
-
puts "#{Time.now.inspect} Deleting workspace (attempt #{attempt
|
|
90
|
+
puts "#{Time.now.inspect} Deleting workspace (attempt #{attempt}/#{5})"
|
|
74
91
|
end
|
|
75
|
-
|
|
76
|
-
@credentials.destroy_credentials(credentials,
|
|
92
|
+
workspace_info = get_workspace_info(id)
|
|
93
|
+
@credentials.destroy_credentials(credentials, workspace_status(workspace_info), id)
|
|
77
94
|
end
|
|
78
95
|
|
|
79
96
|
def wait_for_workspace_deletion(id)
|
|
80
|
-
max_attempts = @
|
|
81
|
-
timeout_interval = @
|
|
97
|
+
max_attempts = @deletion_max_attempts
|
|
98
|
+
timeout_interval = @deletion_timeout_interval_seconds
|
|
82
99
|
|
|
83
100
|
max_attempts.times do |attempt|
|
|
84
|
-
|
|
101
|
+
workspace_info = get_workspace_info(id)
|
|
102
|
+
break unless workspace_info && workspace_status(workspace_info) == "deleting"
|
|
85
103
|
yield(attempt + 1)
|
|
86
104
|
sleep(timeout_interval)
|
|
87
105
|
end
|
|
88
106
|
end
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
107
|
+
def workspace_status(workspace_info)
|
|
108
|
+
workspace_info.dig("latest_build", "status")
|
|
109
|
+
end
|
|
110
|
+
def parse_error_logs(logs_array)
|
|
111
|
+
logs_array
|
|
112
|
+
.reject { |n| n["output"].to_s.empty?}
|
|
113
|
+
.map { |n| n["output"].scan(/"message":\s*"([^"]+)"/)[0] }
|
|
114
|
+
.reject {|n| n.nil?}
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def get_workspace_info(id)
|
|
118
|
+
endpoint = "#{@host}/api/v2/users/#{@service_user}/workspace/#{id}?include_deleted=true"
|
|
92
119
|
headers = get_headers(@token)
|
|
93
120
|
api_call('get', endpoint, headers)
|
|
94
121
|
end
|
|
95
122
|
|
|
123
|
+
def read_coder_output(latest_build)
|
|
124
|
+
coder_output_metadata = latest_build.dig("resources")
|
|
125
|
+
&.find { |resource| resource["name"] == "coder_output" }
|
|
126
|
+
&.dig("metadata")
|
|
127
|
+
coder_output_metadata&.map { |meta| [meta["key"].to_sym, meta["value"]] }&.to_h || {}
|
|
128
|
+
end
|
|
129
|
+
|
|
96
130
|
def info(id)
|
|
97
|
-
|
|
131
|
+
workspace_info = get_workspace_info(id)
|
|
132
|
+
latest_build = workspace_info.dig("latest_build")
|
|
133
|
+
coder_status = workspace_status(workspace_info) || latest_build.dig("job", "status")
|
|
134
|
+
ood_status = coder_state_to_ood_status(coder_status)
|
|
135
|
+
coder_output_hash = read_coder_output(latest_build)
|
|
136
|
+
build_logs = get_build_logs(latest_build.dig("id"))
|
|
137
|
+
error_logs = parse_error_logs(build_logs)
|
|
138
|
+
OodCore::Job::Adapters::Coder::CoderJobInfo.new(**{
|
|
139
|
+
id: workspace_info["id"],
|
|
140
|
+
job_name: workspace_info["workspace_name"],
|
|
141
|
+
status: OodCore::Job::Status.new(state: ood_status),
|
|
142
|
+
job_owner: workspace_info["workspace_owner_name"],
|
|
143
|
+
submission_time: workspace_info["created_at"],
|
|
144
|
+
dispatch_time: workspace_info.dig("updated_at"),
|
|
145
|
+
wallclock_time: wallclock_time(workspace_info, ood_status),
|
|
146
|
+
ood_connection_info: { host: coder_output_hash[:floating_ip], port: 80, error_logs: error_logs},
|
|
147
|
+
native: coder_output_hash
|
|
148
|
+
})
|
|
98
149
|
end
|
|
99
150
|
|
|
100
151
|
def coder_state_to_ood_status(coder_state)
|
|
@@ -114,22 +165,10 @@ class OodCore::Job::Adapters::Coder::Batch
|
|
|
114
165
|
end
|
|
115
166
|
end
|
|
116
167
|
|
|
117
|
-
def
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
coder_output_hash = coder_output_metadata&.map { |meta| [meta["key"].to_sym, meta["value"]] }&.to_h || {}
|
|
122
|
-
OodCore::Job::Adapters::Coder::CoderJobInfo.new(**{
|
|
123
|
-
id: json_data["id"],
|
|
124
|
-
job_name: json_data["workspace_name"],
|
|
125
|
-
status: OodCore::Job::Status.new(state: status),
|
|
126
|
-
job_owner: json_data["workspace_owner_name"],
|
|
127
|
-
submission_time: json_data["created_at"],
|
|
128
|
-
dispatch_time: json_data.dig("updated_at"),
|
|
129
|
-
wallclock_time: wallclock_time(json_data, status),
|
|
130
|
-
ood_connection_info: { host: coder_output_hash[:floating_ip], port: 80 },
|
|
131
|
-
native: coder_output_hash
|
|
132
|
-
})
|
|
168
|
+
def get_build_logs(build_id)
|
|
169
|
+
endpoint = "#{@host}/api/v2/workspacebuilds/#{build_id}/logs"
|
|
170
|
+
headers = get_headers(@token)
|
|
171
|
+
api_call('get', endpoint, headers)
|
|
133
172
|
end
|
|
134
173
|
|
|
135
174
|
def wallclock_time(json_data, status)
|
|
@@ -153,12 +192,6 @@ class OodCore::Job::Adapters::Coder::Batch
|
|
|
153
192
|
et
|
|
154
193
|
end
|
|
155
194
|
|
|
156
|
-
def workspace_info_from_json(json_data)
|
|
157
|
-
state = json_data.dig("latest_build", "status") || json_data.dig("latest_build", "job", "status")
|
|
158
|
-
status = coder_state_to_ood_status(state)
|
|
159
|
-
build_coder_job_info(json_data, status)
|
|
160
|
-
end
|
|
161
|
-
|
|
162
195
|
def api_call(method, endpoint, headers, body = nil)
|
|
163
196
|
uri = URI(endpoint)
|
|
164
197
|
case method.downcase
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
require "fog/openstack"
|
|
2
2
|
require "json"
|
|
3
3
|
require "ood_core/job/adapters/coder/credentials"
|
|
4
|
+
require "tempfile"
|
|
5
|
+
require 'excon'
|
|
4
6
|
|
|
5
7
|
class OpenStackCredentials < CredentialsInterface
|
|
6
|
-
def initialize(auth_url)
|
|
8
|
+
def initialize(auth_url, dir)
|
|
7
9
|
@auth_url = auth_url
|
|
10
|
+
@dir = dir
|
|
8
11
|
end
|
|
9
12
|
|
|
10
|
-
def load_credentials(id
|
|
11
|
-
file_path
|
|
12
|
-
JSON.parse(File.read(file_path))
|
|
13
|
+
def load_credentials(id)
|
|
14
|
+
JSON.parse(File.read(file_path(id)))
|
|
13
15
|
rescue Errno::ENOENT => e
|
|
14
16
|
puts "Error loading credentials: #{e}"
|
|
15
17
|
nil
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
+
def file_path(id)
|
|
21
|
+
return "#{@dir}/#{username}-#{id}-os-credentials.json"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def generate_credentials(project_id)
|
|
25
|
+
token_json = JSON.parse(File.read("#{@dir}/#{username}-os-token.json"))
|
|
20
26
|
access_token = token_json["id"]
|
|
21
27
|
user_id = token_json["user_id"]
|
|
22
28
|
connection = Fog::OpenStack::Identity.new({
|
|
@@ -74,13 +80,17 @@ class OpenStackCredentials < CredentialsInterface
|
|
|
74
80
|
|
|
75
81
|
end
|
|
76
82
|
|
|
77
|
-
def save_credentials(id,
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
def save_credentials(id, app_credentials)
|
|
84
|
+
Tempfile.open(["temp", ".json"], "/tmp") do |temp_file|
|
|
85
|
+
temp_file.write(JSON.generate(app_credentials))
|
|
86
|
+
temp_file.chmod(0600)
|
|
87
|
+
temp_file.close
|
|
88
|
+
FileUtils.mv(temp_file.path, file_path(id))
|
|
89
|
+
end
|
|
80
90
|
end
|
|
81
91
|
|
|
82
92
|
|
|
83
|
-
def destroy_credentials(os_app_credentials, deletion_status, id
|
|
93
|
+
def destroy_credentials(os_app_credentials, deletion_status, id)
|
|
84
94
|
return if os_app_credentials.nil?
|
|
85
95
|
|
|
86
96
|
|
|
@@ -88,17 +98,19 @@ class OpenStackCredentials < CredentialsInterface
|
|
|
88
98
|
credentials_to_destroy = find_os_application_credentials(connection, os_app_credentials)
|
|
89
99
|
|
|
90
100
|
if deletion_status != "deleted"
|
|
91
|
-
File.delete(
|
|
101
|
+
File.delete(file_path(id))
|
|
92
102
|
puts "Workspace deletion timed out, credentials with id #{os_app_credentials['id']} of user #{os_app_credentials['user_id']} were not destroyed"
|
|
93
103
|
return
|
|
94
104
|
end
|
|
95
105
|
|
|
96
106
|
begin
|
|
107
|
+
puts "Destroying application credentials with id #{os_app_credentials['id']} and session #{id}}"
|
|
97
108
|
credentials_to_destroy.destroy
|
|
98
|
-
rescue Excon::Error
|
|
109
|
+
rescue Excon::Error => e
|
|
99
110
|
puts "Error destroying application credentials with id #{os_app_credentials['id']} #{e}"
|
|
100
111
|
raise JobAdapterError, e.message
|
|
101
112
|
end
|
|
113
|
+
File.delete(file_path(id))
|
|
102
114
|
end
|
|
103
115
|
|
|
104
116
|
|
|
@@ -116,4 +128,8 @@ class OpenStackCredentials < CredentialsInterface
|
|
|
116
128
|
def find_os_application_credentials(connection, os_app_credentials)
|
|
117
129
|
connection.application_credentials.find_by_id(os_app_credentials['id'], os_app_credentials['user_id'])
|
|
118
130
|
end
|
|
131
|
+
def username
|
|
132
|
+
@username ||= Etc.getlogin
|
|
133
|
+
end
|
|
119
134
|
end
|
|
135
|
+
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
require "ood_core/refinements/hash_extensions"
|
|
2
2
|
require "ood_core/refinements/array_extensions"
|
|
3
3
|
require 'net/http'
|
|
4
|
-
require 'json'
|
|
5
4
|
require 'etc'
|
|
6
5
|
|
|
6
|
+
|
|
7
7
|
module OodCore
|
|
8
8
|
module Job
|
|
9
9
|
class Factory
|
|
@@ -14,7 +14,7 @@ module OodCore
|
|
|
14
14
|
def self.build_coder(config)
|
|
15
15
|
config = config.to_h.symbolize_keys
|
|
16
16
|
if config[:auth]["cloud"] == "openstack"
|
|
17
|
-
credentials = OpenStackCredentials.new(config[:auth]["url"])
|
|
17
|
+
credentials = OpenStackCredentials.new(config[:auth]["url"], config[:auth]["credentials_dir"])
|
|
18
18
|
else
|
|
19
19
|
raise ArgumentError, "Unsupported credentials for cloud type: #{config[:auth]['cloud']}"
|
|
20
20
|
end
|
|
@@ -79,7 +79,7 @@ module OodCore
|
|
|
79
79
|
# adapters can get by without populating the entire Info object
|
|
80
80
|
# @return [Array<Info>] information describing submitted jobs
|
|
81
81
|
def info_all(attrs: nil)
|
|
82
|
-
|
|
82
|
+
[]
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
# Whether the adapter supports job arrays
|
data/lib/ood_core/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ood_core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.31.
|
|
4
|
+
version: 0.31.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Eric Franz
|
|
@@ -10,7 +10,7 @@ authors:
|
|
|
10
10
|
autorequire:
|
|
11
11
|
bindir: exe
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date: 2026-04-
|
|
13
|
+
date: 2026-04-20 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: ood_support
|