ood_core 0.28.0 → 0.30.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc94feaae3ffa1016b59b053179a18322a7e9c4ad1f937776864a2c2b19fc0a1
4
- data.tar.gz: e41085ddcbbfff4a36723da314f41f1010e4db2fb28209ad781d5c2a6516cd46
3
+ metadata.gz: 14dedae6a9b9508f87bb9b950520fb4614f345a90d8841c23836c2650a5b5d2f
4
+ data.tar.gz: 357f6a0eec8b1029f6ad1d32983a55e071b6fda90b453953a8400c5e7f442c68
5
5
  SHA512:
6
- metadata.gz: 85daa1b26fe2b973ecdbb3b9d2f72b37b142795f637d2db0a73611de0b4081c052b732c56e71ea7d37830bcf3211edc6c01109a2d2514b1a2500b38ac9ac6b98
7
- data.tar.gz: 3a2f47afde0ac909f4d3ea6a7ccf2fcf48b8e13de94ad4e550ee1b2492cf28c7dcc72df179755b332b358f49accb72e8c229a1b506de6614148db14233e6c902
6
+ metadata.gz: 0c7782f857836971b0f26207669c9ef21746b0490755a0e86ba0eea6d4b75069c61e7a5c349aea813b4c38f812e099725d97ba0b7d8e916a3cd00e859e12e4eb
7
+ data.tar.gz: 798c9a6709dbba8d704e51942e9677ca632df7c773689dafeaebbe2212f560c2a18643f15dfa8550bdc1bff810eda34b929ce9fd4d84fef10001a68d63533056
@@ -77,6 +77,16 @@ module OodCore
77
77
  end
78
78
  end
79
79
 
80
+ def title
81
+ if !metadata.title.nil?
82
+ metadata.title
83
+ elsif id.to_s.respond_to?(:titleize)
84
+ id.to_s.titleize
85
+ else
86
+ id.to_s
87
+ end
88
+ end
89
+
80
90
  # Metadata that provides extra information about this cluster
81
91
  # @return [OpenStruct] the metadata
82
92
  def metadata
@@ -100,7 +110,7 @@ module OodCore
100
110
  # Build a job adapter from the job configuration
101
111
  # @return [Job::Adapter] the job adapter
102
112
  def job_adapter
103
- Job::Factory.build(job_config)
113
+ Job::Factory.build(job_config.merge({ id: id }))
104
114
  end
105
115
 
106
116
  # Whether the job feature is allowed based on the ACLs
@@ -12,19 +12,10 @@ module OodCore
12
12
  # The QoS values this account can use.
13
13
  attr_reader :qos
14
14
 
15
- # The cluster this account is associated with.
16
- attr_reader :cluster
17
-
18
- # The queue this account can use. nil means there is no queue info
19
- # for this account.
20
- attr_reader :queue
21
-
22
15
  def initialize(**opts)
23
16
  orig_name = opts.fetch(:name, 'unknown')
24
17
  @name = upcase_accounts? ? orig_name.upcase : orig_name
25
18
  @qos = opts.fetch(:qos, [])
26
- @cluster = opts.fetch(:cluster, nil)
27
- @queue = opts.fetch(:queue, nil)
28
19
  end
29
20
 
30
21
  def to_h
@@ -210,7 +210,8 @@ module OodCore
210
210
  ENV["OOD_JOB_NAME_ILLEGAL_CHARS"].to_s
211
211
  end
212
212
 
213
- # Retrieve the accounts available to use for the current user.
213
+ # Retrieve the accounts available to use for the current user.
214
+ # The same account might appear muiltiple times if it has access to multiple clusters.
214
215
  #
215
216
  # Subclasses that do not implement this will return empty arrays.
216
217
  # @return [Array<AccountInfo>] the accounts available to the user.
@@ -1,26 +1,25 @@
1
1
  require "ood_core/refinements/hash_extensions"
2
2
  require "json"
3
+
3
4
 
4
5
  # Utility class for the Coder adapter to interact with the Coders API.
5
6
  class OodCore::Job::Adapters::Coder::Batch
6
7
  require_relative "coder_job_info"
7
8
  class Error < StandardError; end
8
- def initialize(config)
9
+ def initialize(config, credentials)
9
10
  @host = config[:host]
10
11
  @token = config[:token]
12
+ @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
15
+ @credentials = credentials
11
16
  end
12
17
 
13
- def get_os_app_credentials(username, project_id)
14
- credentials_file = File.read("/home/#{username}/application_credentials.json")
15
- credentials = JSON.parse(credentials_file)
16
- credentials.find { |cred| cred["project_id"] == project_id }
17
- end
18
-
19
- def get_rich_parameters(coder_parameters, project_id, os_app_credentials)
18
+ def get_rich_parameters(coder_parameters, project_id, app_credentials)
20
19
  rich_parameter_values = [
21
- { name: "application_credential_name", value: os_app_credentials["name"] },
22
- { name: "application_credential_id", value: os_app_credentials["id"] },
23
- { name: "application_credential_secret", value: os_app_credentials["secret"] },
20
+ { name: "application_credential_name", value: app_credentials[:name] },
21
+ { name: "application_credential_id", value: app_credentials[:id] },
22
+ { name: "application_credential_secret", value: app_credentials[:secret] },
24
23
  {name: "project_id", value: project_id }
25
24
  ]
26
25
  if coder_parameters
@@ -43,34 +42,59 @@ class OodCore::Job::Adapters::Coder::Batch
43
42
  org_id = script.native[:org_id]
44
43
  project_id = script.native[:project_id]
45
44
  coder_parameters = script.native[:coder_parameters]
46
- endpoint = "https://#{@host}/api/v2/organizations/#{org_id}/members/#{username}/workspaces"
47
- os_app_credentials = get_os_app_credentials(username, project_id)
45
+ endpoint = "#{@host}/api/v2/organizations/#{org_id}/members/#{@service_user}/workspaces"
46
+ app_credentials = @credentials.generate_credentials(project_id, username)
48
47
  headers = get_headers(@token)
48
+ workspace_name = "#{username}-#{script.native[:workspace_name]}-#{rand(2_821_109_907_456).to_s(36)}"
49
49
  body = {
50
- template_id: script.native[:template_id],
51
- template_version_name: script.native[:template_version_name],
52
- name: "#{username}-#{script.native[:workspace_name]}-#{rand(2_821_109_907_456).to_s(36)}",
53
- rich_parameter_values: get_rich_parameters(coder_parameters, project_id, os_app_credentials),
50
+ template_version_id: script.native[:template_version_id],
51
+ name: workspace_name,
52
+ rich_parameter_values: get_rich_parameters(coder_parameters, project_id, app_credentials),
54
53
  }
55
54
 
56
55
  resp = api_call('post', endpoint, headers, body)
56
+ @credentials.save_credentials(resp["id"], username, app_credentials)
57
57
  resp["id"]
58
+
58
59
  end
59
60
 
60
61
  def delete(id)
61
- endpoint = "https://#{@host}/api/v2/workspaces/#{id}/builds"
62
+ endpoint = "#{@host}/api/v2/workspaces/#{id}/builds"
62
63
  headers = get_headers(@token)
63
64
  body = {
64
65
  'orphan' => false,
65
66
  'transition' => 'delete'
66
67
  }
67
- res = api_call('post', endpoint, headers, body)
68
+ api_call('post', endpoint, headers, body)
69
+
70
+ credentials = @credentials.load_credentials(id, username)
71
+
72
+ wait_for_workspace_deletion(id) do |attempt|
73
+ puts "#{Time.now.inspect} Deleting workspace (attempt #{attempt + 1}/#{5})"
74
+ end
75
+
76
+ @credentials.destroy_credentials(credentials, workspace_json(id).dig("latest_build", "status"), id, username)
77
+ end
78
+
79
+ def wait_for_workspace_deletion(id)
80
+ max_attempts = @credential_deletion_max_attempts
81
+ timeout_interval = @credential_deletion_timeout_interval
82
+
83
+ max_attempts.times do |attempt|
84
+ break unless workspace_json(id) && workspace_json(id).dig("latest_build", "status") == "deleting"
85
+ yield(attempt + 1)
86
+ sleep(timeout_interval)
87
+ end
68
88
  end
69
89
 
70
- def info(id)
71
- endpoint = "https://#{@host}/api/v2/workspaces/#{id}?include_deleted=true"
90
+ def workspace_json(id)
91
+ endpoint = "#{@host}/api/v2/workspaces/#{id}?include_deleted=true"
72
92
  headers = get_headers(@token)
73
- workspace_info_from_json(api_call('get', endpoint, headers))
93
+ api_call('get', endpoint, headers)
94
+ end
95
+
96
+ def info(id)
97
+ workspace_info_from_json(workspace_json(id))
74
98
  end
75
99
 
76
100
  def coder_state_to_ood_status(coder_state)
@@ -137,7 +161,6 @@ class OodCore::Job::Adapters::Coder::Batch
137
161
 
138
162
  def api_call(method, endpoint, headers, body = nil)
139
163
  uri = URI(endpoint)
140
-
141
164
  case method.downcase
142
165
  when 'get'
143
166
  request = Net::HTTP::Get.new(uri, headers)
@@ -150,11 +173,9 @@ class OodCore::Job::Adapters::Coder::Batch
150
173
  end
151
174
 
152
175
  request.body = body.to_json if body
153
-
154
176
  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
155
177
  http.request(request)
156
178
  end
157
-
158
179
  case response
159
180
  when Net::HTTPSuccess
160
181
  JSON.parse(response.body)
@@ -0,0 +1,16 @@
1
+ class CredentialsInterface
2
+ def load_credentials
3
+ raise NotImplementedError, "#{self.class} must implement #{__method__}"
4
+ end
5
+
6
+ def generate_credentials
7
+ raise NotImplementedError, "#{self.class} must implement #{__method__}"
8
+ end
9
+
10
+ def destroy_credentials
11
+ raise NotImplementedError, "#{self.class} must implement #{__method__}"
12
+ end
13
+ def save_credentials
14
+ raise NotImplementedError, "#{self.class} must implement #{__method__}"
15
+ end
16
+ end
@@ -0,0 +1,118 @@
1
+ require "fog/openstack"
2
+ require "json"
3
+ require "ood_core/job/adapters/coder/credentials"
4
+
5
+ class OpenStackCredentials < CredentialsInterface
6
+ def initialize(auth_url)
7
+ @auth_url = auth_url
8
+ end
9
+
10
+ def load_credentials(id, username)
11
+ file_path = "/home/#{username}/#{id}_credentials.json"
12
+ JSON.parse(File.read(file_path))
13
+ rescue Errno::ENOENT => e
14
+ puts "Error loading credentials: #{e}"
15
+ nil
16
+ end
17
+
18
+ def generate_credentials(project_id, username)
19
+ token_json = JSON.parse(File.read("/home/#{username}/token.json"))
20
+ access_token = token_json["id"]
21
+ user_id = token_json["user_id"]
22
+ connection = Fog::OpenStack::Identity.new({
23
+ openstack_auth_url: @auth_url,
24
+ openstack_management_url: @auth_url,
25
+ openstack_auth_token: access_token,
26
+ })
27
+
28
+ auth = {
29
+ "auth": {
30
+ "identity": {
31
+ "methods": [
32
+ "token"
33
+ ],
34
+ "token": {
35
+ "id": access_token
36
+ }
37
+ },
38
+ "scope": {
39
+ "project": {
40
+ "id": project_id
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ scoped_token = connection.tokens.authenticate(auth)
47
+
48
+
49
+ connection = Fog::OpenStack::Identity.new({
50
+ openstack_auth_url: @auth_url,
51
+ openstack_management_url: @auth_url,
52
+ openstack_auth_token: scoped_token,
53
+ })
54
+
55
+
56
+ app_credentials = {
57
+ "name": "OOD_generated_#{rand(16**8).to_s(16)}" ,
58
+ "description": "Application credential generated via OOD for Coder.",
59
+ "roles": [
60
+ ],
61
+ "unrestricted": true,
62
+ "user_id": user_id
63
+ }
64
+ res = connection.application_credentials.create app_credentials
65
+
66
+ credential_data = {
67
+ id: res.id,
68
+ name: res.name,
69
+ user_id: user_id,
70
+ secret: res.secret
71
+ }
72
+
73
+ credential_data
74
+
75
+ end
76
+
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))
80
+ end
81
+
82
+
83
+ def destroy_credentials(os_app_credentials, deletion_status, id, username)
84
+ return if os_app_credentials.nil?
85
+
86
+
87
+ connection = create_fog_connection(os_app_credentials)
88
+ credentials_to_destroy = find_os_application_credentials(connection, os_app_credentials)
89
+
90
+ if deletion_status != "deleted"
91
+ File.delete("/home/#{username}/#{id}_credentials.json")
92
+ puts "Workspace deletion timed out, credentials with id #{os_app_credentials['id']} of user #{os_app_credentials['user_id']} were not destroyed"
93
+ return
94
+ end
95
+
96
+ begin
97
+ credentials_to_destroy.destroy
98
+ rescue Excon::Error::Forbidden => e
99
+ puts "Error destroying application credentials with id #{os_app_credentials['id']} #{e}"
100
+ end
101
+ end
102
+
103
+
104
+ private
105
+ def create_fog_connection(os_app_credentials)
106
+ Fog::OpenStack::Identity.new({
107
+ openstack_auth_url: @auth_url,
108
+ openstack_management_url: @auth_url,
109
+ openstack_application_credential_id: os_app_credentials['id'],
110
+ openstack_application_credential_secret: os_app_credentials['secret']
111
+ })
112
+ end
113
+
114
+ private
115
+ def find_os_application_credentials(connection, os_app_credentials)
116
+ connection.application_credentials.find_by_id(os_app_credentials['id'], os_app_credentials['user_id'])
117
+ end
118
+ end
@@ -9,8 +9,16 @@ module OodCore
9
9
  class Factory
10
10
  using Refinements::HashExtensions
11
11
 
12
+ require "ood_core/job/adapters/coder/openstack_credentials"
13
+
12
14
  def self.build_coder(config)
13
- batch = Adapters::Coder::Batch.new(config.to_h.symbolize_keys)
15
+ config = config.to_h.symbolize_keys
16
+ if config[:auth]["cloud"] == "openstack"
17
+ credentials = OpenStackCredentials.new(config[:auth]["url"])
18
+ else
19
+ raise ArgumentError, "Unsupported credentials for cloud type: #{config[:auth]['cloud']}"
20
+ end
21
+ batch = Adapters::Coder::Batch.new(config.to_h.symbolize_keys, credentials)
14
22
  Adapters::Coder.new(batch)
15
23
  end
16
24
  end
@@ -18,7 +26,7 @@ module OodCore
18
26
  module Adapters
19
27
  attr_reader :host, :token
20
28
 
21
- # The adapter class for Kubernetes.
29
+ # The adapter class for Coder.
22
30
  class Coder < Adapter
23
31
 
24
32
  using Refinements::ArrayExtensions