smart_proxy_container_gateway 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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