smart_proxy_container_gateway 1.2.0 → 2.0.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: 3d41b324aaa36809e103fc5a20cf934ed6821825355d6968e140f1ec1135c347
4
- data.tar.gz: 645b65950210122681aed76963da3a95cb48ef75284c139046c992ff49bfa30f
3
+ metadata.gz: fee65676d4362e1328671832a7bf3308b193ab1265ea5626eb52d5300ab7d35a
4
+ data.tar.gz: fe57f17f732079b2ac27ce70cc7c04ef3ed141283cba58db75676a36dfb4b678
5
5
  SHA512:
6
- metadata.gz: 012b85b6a699b3f3887d3ec69e2995aa230c26be432c4353052946065135cfe5496dac8d27a4c7d3e5533f40dcc6c584fae81e22b695ea7af8e8087b6e80ddc3
7
- data.tar.gz: 0a1ce283eb9b6fbbb9ec3360a16b7b76107b9f294ba3d5a58cac0e9147c94a672643de50027d4086a4701ee01a1c52311f2120b8bbb74f2b870f9a0e7764eba5
6
+ metadata.gz: b6f0716504c021e799a8a7d31390c9d203c248530073211cf24d7659439b1220647306033c4e60bf98b9425d427b822a8f98b6962f3dab788883bc17b3f5e9a8
7
+ data.tar.gz: cfe03eaf3ce5e9ffa23dd355849e81abf249cede80a0f98ac12cf056fc7ec20de81195794e820c44adc9eea9000f176c837b70756670244334b1bbca4690dc54
data/README.md CHANGED
@@ -35,11 +35,23 @@ The Container Gateway plugin requires a Pulp 3 instance to connect to. Related
35
35
 
36
36
  # Database information
37
37
 
38
- SQLite database migrations are completely automated. The plugin checks if the database is up-to-date before each query.
38
+ SQLite and PostgreSQL are supported, with SQLite being the default for development and testing.
39
+ Use PostgreSQL in production for improved performance by adding the following settings:
40
+ ```
41
+ # Example PostgreSQL connection settings (default port is 5432)
42
+ :database_backend: postgresql
43
+ :postgresql_connection_string: postgres://foreman-proxy:changeme@localhost:5432/container_gateway
44
+ ```
45
+
46
+ When switching from SQLite to PostgreSQL, if the PostgreSQL database is empty, the SQLite database will be automatically migrated to PostgreSQL.
47
+ For the migration to work, the sqlite_db_path setting must point to the old SQLite database file if the default (no setting definition) was not used.
48
+ The SQLite database file will be deleted after the migration to PostgreSQL is complete.
49
+
50
+ Database migrations are completely automated. The plugin checks if the database is up-to-date at initialization time.
39
51
 
40
52
  # Katello interaction
41
53
 
42
- Auth information is retrieved from the Katello server during smart proxy sync time and cached in the SQLite database.
54
+ Auth information is retrieved from the Katello server during smart proxy sync time and cached in the database.
43
55
 
44
56
  Logging in with a container client will cause the Container Gateway to fetch a token from Katello using the login information.
45
57
 
@@ -7,6 +7,7 @@ module Proxy
7
7
 
8
8
  default_settings :pulp_endpoint => "https://#{`hostname`.strip}",
9
9
  :katello_registry_path => '/v2/',
10
+ :database_backend => 'sqlite',
10
11
  :sqlite_db_path => '/var/lib/foreman-proxy/smart_proxy_container_gateway.db',
11
12
  :sqlite_timeout => 30_000
12
13
 
@@ -25,6 +26,21 @@ module Proxy
25
26
  validate :pulp_endpoint, url: true
26
27
 
27
28
  rackup_path File.join(__dir__, 'container_gateway_http_config.ru')
29
+
30
+ load_dependency_injection_wirings do |container_instance, settings|
31
+ container_instance.singleton_dependency :database_impl, (lambda do
32
+ Proxy::ContainerGateway::Database.new(
33
+ database_backend: settings[:database_backend], sqlite_db_path: settings[:sqlite_db_path],
34
+ sqlite_timeout: settings[:sqlite_timeout], postgresql_connection_string: settings[:postgresql_connection_string]
35
+ )
36
+ end)
37
+ container_instance.singleton_dependency :container_gateway_main_impl, (lambda do
38
+ Proxy::ContainerGateway::ContainerGatewayMain.new(
39
+ database: container_instance.get_dependency(:database_impl),
40
+ **settings.slice(:pulp_endpoint, :pulp_client_ssl_ca, :pulp_client_ssl_cert, :pulp_client_ssl_key)
41
+ )
42
+ end)
43
+ end
28
44
  end
29
45
  end
30
46
  end
@@ -6,7 +6,6 @@ require 'sinatra'
6
6
  require 'smart_proxy_container_gateway/container_gateway'
7
7
  require 'smart_proxy_container_gateway/container_gateway_main'
8
8
  require 'smart_proxy_container_gateway/foreman_api'
9
- require 'sqlite3'
10
9
 
11
10
  module Proxy
12
11
  module ContainerGateway
@@ -14,15 +13,19 @@ module Proxy
14
13
  include ::Proxy::Log
15
14
  helpers ::Proxy::Helpers
16
15
  helpers ::Sinatra::Authorization::Helpers
16
+ extend ::Proxy::ContainerGateway::DependencyInjection
17
+
18
+ inject_attr :database_impl, :database
19
+ inject_attr :container_gateway_main_impl, :container_gateway_main
17
20
 
18
21
  get '/v1/_ping/?' do
19
- Proxy::ContainerGateway.ping
22
+ container_gateway_main.ping
20
23
  end
21
24
 
22
25
  get '/v2/?' do
23
26
  if auth_header.present? && (auth_header.unauthorized_token? || auth_header.valid_user_token?)
24
27
  response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
25
- Proxy::ContainerGateway.ping
28
+ container_gateway_main.ping
26
29
  else
27
30
  redirect_authorization_headers
28
31
  halt 401, "unauthorized"
@@ -33,7 +36,7 @@ module Proxy
33
36
  repository = params[:splat][0]
34
37
  tag = params[:splat][1]
35
38
  handle_repo_auth(repository, auth_header, request)
36
- redirection_location = Proxy::ContainerGateway.manifests(repository, tag)
39
+ redirection_location = container_gateway_main.manifests(repository, tag)
37
40
  redirect to(redirection_location)
38
41
  end
39
42
 
@@ -41,14 +44,14 @@ module Proxy
41
44
  repository = params[:splat][0]
42
45
  digest = params[:splat][1]
43
46
  handle_repo_auth(repository, auth_header, request)
44
- redirection_location = Proxy::ContainerGateway.blobs(repository, digest)
47
+ redirection_location = container_gateway_main.blobs(repository, digest)
45
48
  redirect to(redirection_location)
46
49
  end
47
50
 
48
51
  get '/v2/*/tags/list/?' do
49
52
  repository = params[:splat][0]
50
53
  handle_repo_auth(repository, auth_header, request)
51
- pulp_response = Proxy::ContainerGateway.tags(repository, params)
54
+ pulp_response = container_gateway_main.tags(repository, params)
52
55
  # "link"=>["<http://pulpcore-api/v2/container-image-name/tags/list?n=100&last=last-tag-name>; rel=\"next\""],
53
56
  # https://docs.docker.com/registry/spec/api/#pagination-1
54
57
  if pulp_response['link'].nil?
@@ -74,19 +77,23 @@ module Proxy
74
77
  end
75
78
  params[:user] = username
76
79
  end
77
- repositories = Proxy::ContainerGateway.v1_search(params)
80
+ repositories = container_gateway_main.v1_search(params)
78
81
 
79
82
  content_type :json
80
- { num_results: repositories.size, query: params[:q], results: repositories }.to_json
83
+ {
84
+ num_results: repositories.size,
85
+ query: params[:q],
86
+ results: repositories.map { |repo_name| { description: '', name: repo_name } }
87
+ }.to_json
81
88
  end
82
89
 
83
90
  get '/v2/_catalog/?' do
84
91
  catalog = []
85
92
  if auth_header.present?
86
93
  if auth_header.unauthorized_token?
87
- catalog = Proxy::ContainerGateway.catalog
94
+ catalog = container_gateway_main.catalog.select_map(::Sequel[:repositories][:name])
88
95
  elsif auth_header.valid_user_token?
89
- catalog = Proxy::ContainerGateway.catalog(auth_header.user)
96
+ catalog = container_gateway_main.catalog(auth_header.user).select_map(::Sequel[:repositories][:name])
90
97
  else
91
98
  redirect_authorization_headers
92
99
  halt 401, "unauthorized"
@@ -131,7 +138,7 @@ module Proxy
131
138
  expires_in = token_response_body.fetch("expires_in", 60)
132
139
  expires_at = token_issue_time + expires_in.seconds
133
140
 
134
- ContainerGateway.insert_token(
141
+ container_gateway_main.insert_token(
135
142
  request.params['account'],
136
143
  token_response_body['token'],
137
144
  expires_at.rfc3339
@@ -141,8 +148,8 @@ module Proxy
141
148
  if repo_response.code.to_i != 200
142
149
  halt repo_response.code.to_i, repo_response.body
143
150
  else
144
- ContainerGateway.update_user_repositories(request.params['account'],
145
- JSON.parse(repo_response.body)['repositories'])
151
+ container_gateway_main.update_user_repositories(request.params['account'],
152
+ JSON.parse(repo_response.body)['repositories'])
146
153
  end
147
154
 
148
155
  # Return the original token response from Katello
@@ -154,13 +161,13 @@ module Proxy
154
161
  do_authorize_any
155
162
 
156
163
  content_type :json
157
- { users: User.map(:name) }.to_json
164
+ { users: database.connection[:users].map(:name) }.to_json
158
165
  end
159
166
 
160
167
  put '/user_repository_mapping/?' do
161
168
  do_authorize_any
162
169
 
163
- ContainerGateway.update_user_repo_mapping(params)
170
+ container_gateway_main.update_user_repo_mapping(params)
164
171
  {}
165
172
  end
166
173
 
@@ -168,7 +175,7 @@ module Proxy
168
175
  do_authorize_any
169
176
 
170
177
  repositories = params['repositories'].nil? ? [] : params['repositories']
171
- ContainerGateway.update_repository_list(repositories)
178
+ container_gateway_main.update_repository_list(repositories)
172
179
  {}
173
180
  end
174
181
 
@@ -176,14 +183,13 @@ module Proxy
176
183
 
177
184
  def handle_repo_auth(repository, auth_header, request)
178
185
  user_token_is_valid = false
179
- # FIXME: Getting unauthenticated token here...
180
186
  if auth_header.present? && auth_header.valid_user_token?
181
187
  user_token_is_valid = true
182
- username = auth_header.user.name
188
+ username = auth_header.user[:name]
183
189
  end
184
190
  username = request.params['account'] if username.nil?
185
191
 
186
- return if Proxy::ContainerGateway.authorized_for_repo?(repository, user_token_is_valid, username)
192
+ return if container_gateway_main.authorized_for_repo?(repository, user_token_is_valid, username)
187
193
 
188
194
  redirect_authorization_headers
189
195
  halt 401, "unauthorized"
@@ -201,6 +207,10 @@ module Proxy
201
207
  end
202
208
 
203
209
  class AuthorizationHeader
210
+ extend ::Proxy::ContainerGateway::DependencyInjection
211
+
212
+ inject_attr :database_impl, :database
213
+ inject_attr :container_gateway_main_impl, :container_gateway_main
204
214
  UNAUTHORIZED_TOKEN = 'unauthorized'.freeze
205
215
 
206
216
  def initialize(value)
@@ -208,11 +218,11 @@ module Proxy
208
218
  end
209
219
 
210
220
  def user
211
- ContainerGateway.token_user(@value.split(' ')[1])
221
+ container_gateway_main.token_user(@value.split(' ')[1])
212
222
  end
213
223
 
214
224
  def valid_user_token?
215
- token_auth? && ContainerGateway.valid_token?(@value.split(' ')[1])
225
+ token_auth? && container_gateway_main.valid_token?(@value.split(' ')[1])
216
226
  end
217
227
 
218
228
  def raw_header
@@ -1,19 +1,31 @@
1
1
  require 'net/http'
2
2
  require 'uri'
3
3
  require 'digest'
4
+ require 'smart_proxy_container_gateway/dependency_injection'
4
5
  require 'sequel'
5
6
  module Proxy
6
7
  module ContainerGateway
7
8
  extend ::Proxy::Util
8
9
  extend ::Proxy::Log
9
10
 
10
- class << self
11
- Sequel.extension :migration, :core_extensions
11
+ class ContainerGatewayMain
12
+ attr_reader :database
13
+
14
+ def initialize(database:, pulp_endpoint:, pulp_client_ssl_ca:, pulp_client_ssl_cert:, pulp_client_ssl_key:)
15
+ @database = database
16
+ @pulp_endpoint = pulp_endpoint
17
+ @pulp_client_ssl_ca = pulp_client_ssl_ca
18
+ @pulp_client_ssl_cert = OpenSSL::X509::Certificate.new(File.read(pulp_client_ssl_cert))
19
+ @pulp_client_ssl_key = OpenSSL::PKey::RSA.new(
20
+ File.read(pulp_client_ssl_key)
21
+ )
22
+ end
23
+
12
24
  def pulp_registry_request(uri)
13
25
  http_client = Net::HTTP.new(uri.host, uri.port)
14
- http_client.ca_file = pulp_ca
15
- http_client.cert = pulp_cert
16
- http_client.key = pulp_key
26
+ http_client.ca_file = @pulp_client_ssl_ca
27
+ http_client.cert = @pulp_client_ssl_cert
28
+ http_client.key = @pulp_client_ssl_key
17
29
  http_client.use_ssl = true
18
30
 
19
31
  http_client.start do |http|
@@ -23,20 +35,20 @@ module Proxy
23
35
  end
24
36
 
25
37
  def ping
26
- uri = URI.parse("#{Proxy::ContainerGateway::Plugin.settings.pulp_endpoint}/pulpcore_registry/v2/")
38
+ uri = URI.parse("#{@pulp_endpoint}/pulpcore_registry/v2/")
27
39
  pulp_registry_request(uri).body
28
40
  end
29
41
 
30
42
  def manifests(repository, tag)
31
43
  uri = URI.parse(
32
- "#{Proxy::ContainerGateway::Plugin.settings.pulp_endpoint}/pulpcore_registry/v2/#{repository}/manifests/#{tag}"
44
+ "#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/manifests/#{tag}"
33
45
  )
34
46
  pulp_registry_request(uri)['location']
35
47
  end
36
48
 
37
49
  def blobs(repository, digest)
38
50
  uri = URI.parse(
39
- "#{Proxy::ContainerGateway::Plugin.settings.pulp_endpoint}/pulpcore_registry/v2/#{repository}/blobs/#{digest}"
51
+ "#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/blobs/#{digest}"
40
52
  )
41
53
  pulp_registry_request(uri)['location']
42
54
  end
@@ -50,51 +62,54 @@ module Proxy
50
62
  query = "#{query}last=#{params[:last]}" unless params[:last].nil? || params[:last] == ""
51
63
 
52
64
  uri = URI.parse(
53
- "#{Proxy::ContainerGateway::Plugin.settings.pulp_endpoint}/pulpcore_registry/v2/#{repository}/tags/list#{query}"
65
+ "#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/tags/list#{query}"
54
66
  )
55
67
  pulp_registry_request(uri)
56
68
  end
57
69
 
58
70
  def v1_search(params = {})
59
71
  if params[:n].nil? || params[:n] == ""
60
- params[:n] = 25
72
+ limit = 25
61
73
  else
62
- params[:n] = params[:n].to_i
74
+ limit = params[:n].to_i
63
75
  end
76
+ return [] unless limit.positive?
64
77
 
65
- repo_count = 0
66
- repositories = []
67
- user = params[:user].nil? ? nil : User.find(name: params[:user])
68
- Proxy::ContainerGateway.catalog(user).each do |repo_name|
69
- break if repo_count >= params[:n]
78
+ query = params[:q]
79
+ query = nil if query == ''
70
80
 
71
- if params[:q].nil? || params[:q] == "" || repo_name.include?(params[:q])
72
- repo_count += 1
73
- repositories << { name: repo_name }
74
- end
75
- end
76
- repositories
81
+ user = params[:user].nil? ? nil : database.connection[:users][{ name: params[:user] }]
82
+
83
+ repositories = query ? catalog(user).grep(:name, "%#{query}%") : catalog(user)
84
+ repositories.limit(limit).select_map(::Sequel[:repositories][:name])
77
85
  end
78
86
 
79
87
  def catalog(user = nil)
80
88
  if user.nil?
81
89
  unauthenticated_repos
82
90
  else
83
- (unauthenticated_repos + user.repositories_dataset.map(:name)).sort
91
+ database.connection[:repositories].
92
+ left_join(:repositories_users, repository_id: :id).
93
+ left_join(:users, ::Sequel[:users][:id] => :user_id).where(user_id: user[:id]).
94
+ or(Sequel[:repositories][:auth_required] => false).order(::Sequel[:repositories][:name])
84
95
  end
85
96
  end
86
97
 
87
98
  def unauthenticated_repos
88
- Repository.where(auth_required: false).order(:name).map(:name)
99
+ database.connection[:repositories].where(auth_required: false).order(:name)
89
100
  end
90
101
 
91
102
  # Replaces the entire list of repositories
92
103
  def update_repository_list(repo_list)
93
- RepositoryUser.dataset.delete
94
- Repository.dataset.delete
95
- repo_list.each do |repo|
96
- Repository.find_or_create(name: repo['repository'],
97
- auth_required: repo['auth_required'].to_s.downcase == "true")
104
+ # repositories_users cascades on deleting repositories (or users)
105
+ database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
106
+ repository = database.connection[:repositories]
107
+ repository.delete
108
+
109
+ repository.import(
110
+ %i[name auth_required],
111
+ repo_list.map { |repo| [repo['repository'], repo['auth_required'].to_s.downcase == "true"] }
112
+ )
98
113
  end
99
114
  end
100
115
 
@@ -103,122 +118,93 @@ module Proxy
103
118
  # Get hash map of all users and their repositories
104
119
  # Ex: {"users"=> [{"admin"=>[{"repository"=>"repo", "auth_required"=>"true"}]}]}
105
120
  # Go through list of repositories and add them to the DB
106
- RepositoryUser.dataset.delete
107
- user_repo_maps['users'].each do |user_repo_map|
108
- user_repo_map.each do |user, repos|
109
- next if repos.nil?
110
-
111
- repos.each do |repo|
112
- found_repo = Repository.find(name: repo['repository'],
113
- auth_required: repo['auth_required'].to_s.downcase == "true")
114
- if found_repo.nil?
115
- logger.warn("#{repo['repository']} does not exist in this smart proxy's environments")
116
- elsif found_repo.auth_required
117
- found_repo.add_user(User.find(name: user))
118
- end
121
+ repositories = database.connection[:repositories]
122
+
123
+ entries = user_repo_maps['users'].flat_map do |user_repo_map|
124
+ user_repo_map.filter_map do |username, repos|
125
+ user_repo_names = repos.filter { |repo| repo['auth_required'].to_s.downcase == "true" }.map do |repo|
126
+ repo['repository']
119
127
  end
128
+ user = database.connection[:users][{ name: username }]
129
+ repositories.where(name: user_repo_names, auth_required: true).select(:id).map { |repo| [repo[:id], user[:id]] }
120
130
  end
121
131
  end
132
+ entries.flatten!(1)
133
+
134
+ repositories_users = database.connection[:repositories_users]
135
+ database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
136
+ repositories_users.delete
137
+ repositories_users.import(%i[repository_id user_id], entries)
138
+ end
122
139
  end
123
140
 
124
141
  # Replaces the user-repo mapping for a single user
125
142
  def update_user_repositories(username, repositories)
126
- user = User.where(name: username).first
127
- user.remove_all_repositories
128
- repositories.each do |repo_name|
129
- found_repo = Repository.find(name: repo_name)
130
- if found_repo.nil?
131
- logger.warn("#{repo_name} does not exist in this smart proxy's environments")
132
- elsif user.repositories_dataset.where(name: repo_name).first.nil? && found_repo.auth_required
133
- user.add_repository(found_repo)
134
- end
143
+ user = database.connection[:users][{ name: username }]
144
+
145
+ user_repositories = database.connection[:repositories_users]
146
+ database.connection.transaction(isolation: :serializable,
147
+ retry_on: [Sequel::SerializationFailure],
148
+ num_retries: 10) do
149
+ user_repositories.where(user_id: user[:id]).delete
150
+
151
+ user_repositories.import(
152
+ %i[repository_id user_id],
153
+ database.connection[:repositories].where(name: repositories, auth_required: true).select(:id).map do |repo|
154
+ [repo[:id], user[:id]]
155
+ end
156
+ )
135
157
  end
136
158
  end
137
159
 
138
160
  def authorized_for_repo?(repo_name, user_token_is_valid, username = nil)
139
- repository = Repository.where(name: repo_name).first
161
+ repository = database.connection[:repositories][{ name: repo_name }]
140
162
 
141
163
  # Repository doesn't exist
142
164
  return false if repository.nil?
143
165
 
144
166
  # Repository doesn't require auth
145
- return true unless repository.auth_required
167
+ return true unless repository[:auth_required]
146
168
 
147
- if username && user_token_is_valid && repository.auth_required
169
+ if username && user_token_is_valid
148
170
  # User is logged in and has access to the repository
149
- user = User.find(name: username)
150
- return !user.repositories_dataset.where(name: repo_name).first.nil?
171
+ return !database.connection[:repositories_users].where(
172
+ repository_id: repository[:id], user_id: database.connection[:users].first(name: username)[:id]
173
+ ).empty?
151
174
  end
152
175
 
153
176
  false
154
177
  end
155
178
 
156
179
  def token_user(token)
157
- User[AuthenticationToken.find(token_checksum: Digest::SHA256.hexdigest(token)).user_id]
180
+ database.connection[:users][{
181
+ id: database.connection[:authentication_tokens].where(token_checksum: checksum(token)).select(:user_id)
182
+ }]
158
183
  end
159
184
 
160
185
  def valid_token?(token)
161
- AuthenticationToken.where(token_checksum: Digest::SHA256.hexdigest(token)).where do
186
+ !database.connection[:authentication_tokens].where(token_checksum: checksum(token)).where do
162
187
  expire_at > Sequel::CURRENT_TIMESTAMP
163
- end.count.positive?
188
+ end.empty?
164
189
  end
165
190
 
166
191
  def insert_token(username, token, expire_at_string, clear_expired_tokens: true)
167
192
  checksum = Digest::SHA256.hexdigest(token)
168
- user = User.find_or_create(name: username)
169
-
170
- AuthenticationToken.where(:token_checksum => checksum).delete
171
- AuthenticationToken.create(token_checksum: checksum, expire_at: expire_at_string.to_s, user_id: user.id)
172
- AuthenticationToken.where { expire_at < Sequel::CURRENT_TIMESTAMP }.delete if clear_expired_tokens
173
- end
174
-
175
- def initialize_db
176
- file_path = Proxy::ContainerGateway::Plugin.settings.sqlite_db_path
177
- sqlite_timeout = Proxy::ContainerGateway::Plugin.settings.sqlite_timeout
178
- conn = Sequel.connect("sqlite://#{file_path}", timeout: sqlite_timeout)
179
- container_gateway_path = $LOAD_PATH.detect { |path| path.include? 'smart_proxy_container_gateway' }
180
- begin
181
- Sequel::Migrator.check_current(conn, "#{container_gateway_path}/smart_proxy_container_gateway/sequel_migrations")
182
- rescue Sequel::Migrator::NotCurrentError
183
- migrate_db(conn, container_gateway_path)
184
- end
185
- conn
186
- end
193
+ user = Sequel::Model(database.connection[:users]).find_or_create(name: username)
187
194
 
188
- private
189
-
190
- def migrate_db(db_connection, container_gateway_path)
191
- Sequel::Migrator.run(db_connection, "#{container_gateway_path}/smart_proxy_container_gateway/sequel_migrations")
192
- end
195
+ database.connection[:authentication_tokens].where(:token_checksum => checksum).delete
196
+ Sequel::Model(database.connection[:authentication_tokens]).
197
+ create(token_checksum: checksum, expire_at: expire_at_string.to_s, user_id: user.id)
198
+ return unless clear_expired_tokens
193
199
 
194
- def pulp_ca
195
- Proxy::ContainerGateway::Plugin.settings.pulp_client_ssl_ca
200
+ database.connection[:authentication_tokens].where { expire_at < Sequel::CURRENT_TIMESTAMP }.delete
196
201
  end
197
202
 
198
- def pulp_cert
199
- OpenSSL::X509::Certificate.new(File.read(Proxy::ContainerGateway::Plugin.settings.pulp_client_ssl_cert))
200
- end
203
+ private
201
204
 
202
- def pulp_key
203
- OpenSSL::PKey::RSA.new(
204
- File.read(Proxy::ContainerGateway::Plugin.settings.pulp_client_ssl_key)
205
- )
205
+ def checksum(token)
206
+ Digest::SHA256.hexdigest(token)
206
207
  end
207
208
  end
208
-
209
- class Repository < ::Sequel::Model(Proxy::ContainerGateway.initialize_db[:repositories])
210
- many_to_many :users
211
- end
212
-
213
- class User < ::Sequel::Model(Proxy::ContainerGateway.initialize_db[:users])
214
- many_to_many :repositories
215
- one_to_many :authentication_tokens
216
- end
217
-
218
- class RepositoryUser < ::Sequel::Model(Proxy::ContainerGateway.initialize_db[:repositories_users]); end
219
-
220
- class AuthenticationToken < ::Sequel::Model(Proxy::ContainerGateway.initialize_db[:authentication_tokens])
221
- many_to_one :users
222
- end
223
209
  end
224
210
  end
@@ -0,0 +1,53 @@
1
+ require 'sequel'
2
+ module Proxy
3
+ module ContainerGateway
4
+ class Database
5
+ attr_reader :connection
6
+
7
+ def initialize(options = {})
8
+ if options[:database_backend] == 'sqlite'
9
+ @connection = Sequel.connect("sqlite://#{options[:sqlite_db_path]}", timeout: options[:sqlite_timeout])
10
+ @connection.run("PRAGMA foreign_keys = ON;")
11
+ @connection.run("PRAGMA journal_mode = wal;")
12
+ else
13
+ unless options[:postgresql_connection_string]
14
+ raise ArgumentError, 'PostgreSQL connection string is required'
15
+ end
16
+
17
+ @connection = Sequel.connect(options[:postgresql_connection_string])
18
+ if File.exist?(options[:sqlite_db_path]) &&
19
+ (!@connection.table_exists?(:repositories) || @connection[:repositories].count.zero?)
20
+ migrate_to_postgres(Sequel.sqlite(options[:sqlite_db_path]), @connection)
21
+ File.delete(options[:sqlite_db_path])
22
+ end
23
+ end
24
+ migrate
25
+ end
26
+
27
+ private
28
+
29
+ def migrate
30
+ Sequel.extension :migration, :core_extensions
31
+ migration_path = File.join(__dir__, 'sequel_migrations')
32
+ begin
33
+ Sequel::Migrator.check_current(@connection, migration_path)
34
+ rescue Sequel::Migrator::NotCurrentError
35
+ Sequel::Migrator.run(@connection, migration_path)
36
+ end
37
+ end
38
+
39
+ def migrate_to_postgres(sqlite_db, postgres_db)
40
+ migrate
41
+ sqlite_db.transaction do
42
+ sqlite_db.tables.each do |table|
43
+ next if table == :schema_info
44
+
45
+ sqlite_db[table].each do |row|
46
+ postgres_db[table.to_sym].insert(row)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,10 @@
1
+ module Proxy
2
+ module ContainerGateway
3
+ module DependencyInjection
4
+ include Proxy::DependencyInjection::Accessors
5
+ def container_instance
6
+ @container_instance ||= ::Proxy::Plugins.instance.find { |p| p[:name] == :container_gateway }[:di_container]
7
+ end
8
+ end
9
+ end
10
+ end
@@ -25,8 +25,8 @@ Sequel.migration do
25
25
  end
26
26
 
27
27
  # Populate the new user_id foreign key for all authentication_tokens
28
- from(:authentication_tokens).insert([:user_id],
29
- from(:users).select(:id).where(name: self[:authentication_tokens][:username]))
28
+ from(:authentication_tokens).
29
+ update(user_id: from(:users).select(:id).where(name: Sequel[:authentication_tokens][:username]))
30
30
 
31
31
  alter_table(:authentication_tokens) do
32
32
  drop_column :username
@@ -0,0 +1,28 @@
1
+ Sequel.migration do
2
+ up do
3
+ # SQLite does not support dropping columns until version 3.35, which is not in the EL8 ecosystem as of March, 2024.
4
+ create_table(:repositories_users2) do
5
+ foreign_key :repository_id, :repositories, on_delete: :cascade
6
+ foreign_key :user_id, :users, on_delete: :cascade
7
+ primary_key %i[repository_id user_id]
8
+ index %i[repository_id user_id]
9
+ end
10
+ run "INSERT INTO repositories_users2(repository_id, user_id) SELECT repository_id, user_id from repositories_users"
11
+
12
+ drop_table(:repositories_users)
13
+ rename_table(:repositories_users2, :repositories_users)
14
+ end
15
+
16
+ down do
17
+ create_table(:repositories_users2) do
18
+ foreign_key :repository_id, :repositories
19
+ foreign_key :user_id, :users
20
+ primary_key %i[repository_id user_id]
21
+ index %i[repository_id user_id]
22
+ end
23
+ run "INSERT INTO repositories_users2(repository_id, user_id) SELECT repository_id, user_id from repositories_users"
24
+
25
+ drop_table(:repositories_users)
26
+ rename_table(:repositories_users2, :repositories_users)
27
+ end
28
+ end
@@ -1,5 +1,5 @@
1
1
  module Proxy
2
2
  module ContainerGateway
3
- VERSION = '1.2.0'.freeze
3
+ VERSION = '2.0.0'.freeze
4
4
  end
5
5
  end
@@ -1,2 +1,4 @@
1
1
  require 'smart_proxy_container_gateway/version'
2
+ require 'smart_proxy_container_gateway/database'
2
3
  require 'smart_proxy_container_gateway/container_gateway'
4
+ require 'smart_proxy_container_gateway/container_gateway_main'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_container_gateway
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ian Ballou
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-09 00:00:00.000000000 Z
11
+ date: 2024-04-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: sequel
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -68,10 +82,13 @@ files:
68
82
  - lib/smart_proxy_container_gateway/container_gateway_api.rb
69
83
  - lib/smart_proxy_container_gateway/container_gateway_http_config.ru
70
84
  - lib/smart_proxy_container_gateway/container_gateway_main.rb
85
+ - lib/smart_proxy_container_gateway/database.rb
86
+ - lib/smart_proxy_container_gateway/dependency_injection.rb
71
87
  - lib/smart_proxy_container_gateway/foreman_api.rb
72
88
  - lib/smart_proxy_container_gateway/sequel_migrations/001_initial.rb
73
89
  - lib/smart_proxy_container_gateway/sequel_migrations/002_auth_tokens.rb
74
90
  - lib/smart_proxy_container_gateway/sequel_migrations/003_authorization_reorg.rb
91
+ - lib/smart_proxy_container_gateway/sequel_migrations/004_users_repositories_cascade_delete.rb
75
92
  - lib/smart_proxy_container_gateway/version.rb
76
93
  - settings.d/container_gateway.yml.example
77
94
  homepage: https://github.com/Katello/smart_proxy_container_gateway
@@ -93,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
110
  - !ruby/object:Gem::Version
94
111
  version: '0'
95
112
  requirements: []
96
- rubygems_version: 3.4.10
113
+ rubygems_version: 3.4.21
97
114
  signing_key:
98
115
  specification_version: 4
99
116
  summary: Pulp 3 container registry support for Foreman/Katello Smart-Proxy