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: d1ad113f64d7ee39779802e7386bf753a2323969302963c58456b51a05f132b5
4
- data.tar.gz: 472bcdea0fbd7096171a8338507e0e9c6554d6a4ceea3fb12f46a4a59f48cf7e
3
+ metadata.gz: a2dab30b28f29c33b19e8cce3b5464805c09ba374336ac68fa27da684826fca6
4
+ data.tar.gz: f94e04cb03fa816f4fa54f72e43acdfac2e5a3ee301bd0de59624ed4367669f7
5
5
  SHA512:
6
- metadata.gz: 6436bdfb152bd592627f6fb710d44fc08e10d240a398199081ffd0ba1e3fd3a6f4fbaf8fb56adb88656bb58557eb6630702baea2e07ab1e5072f9180e02da967
7
- data.tar.gz: 12eb878a31d953d7fb4c8bb6a0e67febf42b5c04aa397aa9fb17329d9aae33601e2d7358c3799ede4be5ef8af84fc9cb8d44e022847a4d42f3162788e5db828d
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
- @credential_deletion_max_attempts = config[:credential_deletion_max_attempts] || 5
14
- @credential_deletion_timeout_interval = config[:credential_deletion_timeout_interval] || 10
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
- coder_parameters = script.native[:coder_parameters]
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: script.native[:template_version_id],
51
- name: workspace_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
- end
72
+ def delete_coder_workspace(id)
73
+ build_id = get_workspace_info(id)["id"]
60
74
 
61
- def delete(id)
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
- credentials = @credentials.load_credentials(id, username)
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 + 1}/#{5})"
90
+ puts "#{Time.now.inspect} Deleting workspace (attempt #{attempt}/#{5})"
74
91
  end
75
-
76
- @credentials.destroy_credentials(credentials, workspace_json(id).dig("latest_build", "status"), id, username)
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 = @credential_deletion_max_attempts
81
- timeout_interval = @credential_deletion_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
- break unless workspace_json(id) && workspace_json(id).dig("latest_build", "status") == "deleting"
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
- def workspace_json(id)
91
- endpoint = "#{@host}/api/v2/workspaces/#{id}?include_deleted=true"
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
- workspace_info_from_json(workspace_json(id))
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 build_coder_job_info(json_data, status)
118
- coder_output_metadata = json_data["latest_build"]["resources"]
119
- &.find { |resource| resource["name"] == "coder_output" }
120
- &.dig("metadata")
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, username)
11
- file_path = "/home/#{username}/#{id}_credentials.json"
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 generate_credentials(project_id, username)
19
- token_json = JSON.parse(File.read("/home/#{username}/token.json"))
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, username, app_credentials)
78
- file_path = "/home/#{username}/#{id}_credentials.json"
79
- File.write(file_path, JSON.generate(app_credentials))
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, username)
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("/home/#{username}/#{id}_credentials.json")
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::Forbidden => e
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
- raise NotImplementedError, 'subclass did not define #info_all'
82
+ []
83
83
  end
84
84
 
85
85
  # Whether the adapter supports job arrays
@@ -881,8 +881,8 @@ module OodCore
881
881
 
882
882
  # safely parse date time string, return nil when there are errors.
883
883
  def parse_time(date_time)
884
- Time.parse(date_time)
885
- rescue ArgumentError
884
+ Time.parse(date_time.to_s)
885
+ rescue StandardError
886
886
  nil
887
887
  end
888
888
 
@@ -1,4 +1,4 @@
1
1
  module OodCore
2
2
  # The current version of {OodCore}
3
- VERSION = "0.31.0"
3
+ VERSION = "0.31.1"
4
4
  end
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.0
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-16 00:00:00.000000000 Z
13
+ date: 2026-04-20 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: ood_support