Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refs #37455 - Create Katello push repositories as needed at container push time #10995

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

qcjames53
Copy link
Contributor

-----This PR is not yet feature complete. This is likely broken in a few ways and still needs unit tests.-----

What are the changes introduced in this pull request?

  • When using podman to push containers to Katello, ensure that the container name is of a valid format:
    • <org_label>/<product_label>/<container_name>[:tag] (down-case all letters)
    • id/<org_id>/<product_id>/<container_name>[:tag]
  • Create a new push repository matching the container name under the appropriate product where required.
  • Update existing push repos where required.
  • Run content indexing after completion of the container upload.
  • Ensure the root repository fields are correct after upload:
    • name: matches container_name
    • label: matches container_name
    • content_type: docker
    • product_id: matches product
  • Ensure that the repository fields are correct after upload:
    • relative_path: matches the uploaded container name (exactly what the user entered to podman, either format)
    • environment_id: correct for product
    • content_view_version_id: correct for product
    • version_href: pulp container href
    • container_repository_name: <org_label>/<product_label>/<container_name> with proper capitalization OR follows LE pattern (further discussion needed)
  • Ensure the repository is pushed to a capsule if a user has "Sync Smart Proxies after content view promotion" enabled.

Considerations taken when implementing this change?

TODO - section will change as PR changes

What are the testing steps for this pull request?

  1. Pull a few different podman images:
podman pull quay.io/aptible/hello-world
  1. Launch katello and create an empty product. Log in with podman:
podman login <url>
  1. Get the image ID for one of the test podman images. Push that image to katello using the correct organization label and product label. Make sure to down-case the labels or podman will reject the container name:
podman image list
podman push <img_id> <url>/<organization_label>/<product_label>/<container_name>
  1. Use the rake console to ensure the root repository and repository were created properly:
bundle exec rake console
::Katello::Products.find(<id>).root_repositories.where(label: "<container_name>").first
::Katello::Products.find(<id>).root_repositories.where(label: "<container_name>").first.library_instance
  1. Repeat steps 3 and 4 with another image and the same name. It should update the repo without error. Certain fields will change in the rake console.
  2. Push images with invalid product labels and organization labels. Try different formats with differing numbers of forward slashes, etc. These pushes should error out.
  3. Create two products with the same name but different capitalization (Katello should allow this). Push an image to these products using the label. This push should error out because of ambiguity in the down-cased label name.
  4. Repeat steps 3 and 4 on the ambiguous product using the id format instead of the label format. This should work:
podman push <img_id> <url>/id/<organization_id>/<product_id>/<container_name>
  1. Attempt to push without being authenticated and with a user without product edit permissions. These pushes should error out.
  2. Repeat steps 3 and 4 with a [:tag] field. This should work as usual.
  3. Enable 'Sync Smart Proxies after content view promotion' and push a container. Ensure the connected capsule receives the repo.
  4. Validate the test cases for coverage and accuracy.

@qcjames53
Copy link
Contributor Author

The errors thrown are a little messy. They are all generic errors and they tend to get truncated on the podman side. I'm happy to improve them if you think it's a good idea.

@qcjames53
Copy link
Contributor Author

Containers with tags such as default_organization/dummy_product/foo:bar end up losing the tag when pushed. I think this is okay since the tag is on the podman side; however, this means that users would technically need to pull using a different name than the name they pushed with. This may need further discussion since I could see users expecting either behavior (tag loss vs tag specification on pull).

Copy link
Member

@ianballou ianballou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw this error on my first push:

container_repository_name: ["for repository 'fedora-bootc' is not unique and cannot be created in 'Library'. Its Container Repository Name (default_organization-buttermilk_biscuits-fedora-bootc) conflicts with an existing repository.  Consider changing the Lifecycle Environment's Registry Name Pattern to something more specific."]

I do have another container repo, but its label is centos-bootc_fedora-bootc

The error didn't throw until later. Was repo creation attempted twice maybe?

I pushed:

podman push 75eca4dcedfb `hostname`/default_organization/buttermilk_biscuits/fedora-bootc

@qcjames53
Copy link
Contributor Author

qcjames53 commented May 14, 2024

I saw this error on my first push:

container_repository_name: ["for repository 'fedora-bootc' is not unique and cannot be created in 'Library'. Its Container Repository Name (default_organization-buttermilk_biscuits-fedora-bootc) conflicts with an existing repository.  Consider changing the Lifecycle Environment's Registry Name Pattern to something more specific."]

I do have another container repo, but its label is centos-bootc_fedora-bootc

The error didn't throw until later. Was repo creation attempted twice maybe?

I pushed:

podman push 75eca4dcedfb `hostname`/default_organization/buttermilk_biscuits/fedora-bootc

Well shoot. Podman calls the endpoint that calls start_upload_blob multiple times. Sometimes once, sometimes 3 or 4 times. Big pain in the butt. If I were a betting person, the logic in that method is getting tripped up and is unable to find the repo created on the previous run and is trying to create the repo again.

Another option is that the centos-bootc_fedora-bootc is named fedora-bootc because of a weird LE pattern?

@ianballou
Copy link
Member

I deleted all of my repos and tried again, ended up hitting:

2024-05-14T21:18:25 [E|kat|2a1f0b55] NoMethodError: undefined method `library_instance' for nil:NilClass
 2a1f0b55 | /home/vagrant/katello/app/controllers/katello/api/registry/registry_proxies_controller.rb:397:in `push_manifest'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/abstract_controller/base.rb:228:in `process_action'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_controller/metal/rendering.rb:30:in `process_action'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/abstract_controller/callbacks.rb:42:in `block in process_action'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:117:in `block in run_callbacks'
 2a1f0b55 | /home/vagrant/katello/app/controllers/katello/api/registry/registry_proxies_controller.rb:24:in `repackage_message'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
 2a1f0b55 | /home/vagrant/foreman/app/controllers/concerns/foreman/controller/timezone.rb:10:in `set_timezone'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
 2a1f0b55 | /home/vagrant/foreman/app/models/concerns/foreman/thread_session.rb:32:in `clear_thread'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
 2a1f0b55 | /home/vagrant/foreman/app/controllers/concerns/foreman/controller/topbar_sweeper.rb:12:in `set_topbar_sweeper_controller'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/audited-5.6.0/lib/audited/sweeper.rb:16:in `around'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/audited-5.6.0/lib/audited/sweeper.rb:16:in `around'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:137:in `run_callbacks'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/abstract_controller/callbacks.rb:41:in `process_action'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_controller/metal/rescue.rb:22:in `process_action'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activesupport-6.1.7.7/lib/active_support/notifications.rb:203:in `block in instrument'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activesupport-6.1.7.7/lib/active_support/notifications/instrumenter.rb:24:in `instrument'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activesupport-6.1.7.7/lib/active_support/notifications.rb:203:in `instrument'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_controller/metal/instrumentation.rb:33:in `process_action'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_controller/metal/params_wrapper.rb:249:in `process_action'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activerecord-6.1.7.7/lib/active_record/railties/controller_runtime.rb:27:in `process_action'
 2a1f0b55 | /home/vagrant/katello/app/controllers/katello/api/registry/registry_proxies_controller.rb:581:in `call'
 2a1f0b55 | /home/vagrant/katello/app/controllers/katello/api/registry/registry_proxies_controller.rb:581:in `process_action'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/abstract_controller/base.rb:165:in `process'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionview-6.1.7.7/lib/action_view/rendering.rb:39:in `process'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_controller/metal.rb:190:in `dispatch'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_controller/metal.rb:254:in `dispatch'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/routing/route_set.rb:50:in `dispatch'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/routing/route_set.rb:33:in `serve'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/journey/router.rb:50:in `block in serve'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/journey/router.rb:32:in `each'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/journey/router.rb:32:in `serve'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/routing/route_set.rb:842:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/railties-6.1.7.7/lib/rails/engine.rb:539:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/railties-6.1.7.7/lib/rails/railtie.rb:207:in `public_send'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/railties-6.1.7.7/lib/rails/railtie.rb:207:in `method_missing'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/routing/mapper.rb:20:in `block in <class:Constraints>'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/routing/mapper.rb:49:in `serve'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/journey/router.rb:50:in `block in serve'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/journey/router.rb:32:in `each'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/journey/router.rb:32:in `serve'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/routing/route_set.rb:842:in `call'
 2a1f0b55 | /home/vagrant/katello/lib/katello/middleware/organization_created_enforcer.rb:18:in `call'
 2a1f0b55 | /home/vagrant/katello/lib/katello/middleware/event_daemon.rb:10:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/static.rb:24:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/static.rb:24:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/apipie-dsl-2.6.2/lib/apipie_dsl/static_dispatcher.rb:67:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/apipie-rails-1.3.0/lib/apipie/static_dispatcher.rb:74:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/bullet-7.1.6/lib/bullet/rack.rb:17:in `call'
 2a1f0b55 | /home/vagrant/foreman/lib/foreman/middleware/libvirt_connection_cleaner.rb:9:in `call'
 2a1f0b55 | /home/vagrant/foreman/lib/foreman/middleware/telemetry.rb:10:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/apipie-rails-1.3.0/lib/apipie/middleware/checksum_in_headers.rb:27:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/rack-openid-1.4.2/lib/rack/openid.rb:98:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/rack-2.2.9/lib/rack/tempfile_reaper.rb:15:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/rack-2.2.9/lib/rack/etag.rb:27:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/rack-2.2.9/lib/rack/conditional_get.rb:40:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/rack-2.2.9/lib/rack/head.rb:12:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/http/permissions_policy.rb:22:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/http/content_security_policy.rb:19:in `call'
 2a1f0b55 | /home/vagrant/foreman/lib/foreman/middleware/logging_context_session.rb:22:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/rack-2.2.9/lib/rack/session/abstract/id.rb:266:in `context'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/rack-2.2.9/lib/rack/session/abstract/id.rb:260:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/cookies.rb:697:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activerecord-6.1.7.7/lib/active_record/migration.rb:601:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/callbacks.rb:27:in `block in call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:98:in `run_callbacks'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/callbacks.rb:26:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/executor.rb:14:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/actionable_exceptions.rb:18:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/debug_exceptions.rb:29:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/railties-6.1.7.7/lib/rails/rack/logger.rb:37:in `call_app'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/railties-6.1.7.7/lib/rails/rack/logger.rb:28:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/sprockets-rails-3.4.2/lib/sprockets/rails/quiet_assets.rb:13:in `call'
 2a1f0b55 | /home/vagrant/foreman/lib/foreman/middleware/logging_context_request.rb:11:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/request_id.rb:26:in `call'
 2a1f0b55 | /home/vagrant/katello/lib/katello/prevent_json_parsing.rb:12:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/rack-2.2.9/lib/rack/method_override.rb:24:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/rack-2.2.9/lib/rack/runtime.rb:22:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/activesupport-6.1.7.7/lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/executor.rb:14:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/static.rb:24:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/rack-2.2.9/lib/rack/sendfile.rb:110:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/actionpack-6.1.7.7/lib/action_dispatch/middleware/host_authorization.rb:148:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/secure_headers-6.5.0/lib/secure_headers/middleware.rb:11:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/railties-6.1.7.7/lib/rails/engine.rb:539:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/railties-6.1.7.7/lib/rails/railtie.rb:207:in `public_send'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/railties-6.1.7.7/lib/rails/railtie.rb:207:in `method_missing'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/rack-2.2.9/lib/rack/urlmap.rb:74:in `block in call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/rack-2.2.9/lib/rack/urlmap.rb:58:in `each'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/rack-2.2.9/lib/rack/urlmap.rb:58:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/puma-6.4.2/lib/puma/configuration.rb:272:in `call'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/puma-6.4.2/lib/puma/request.rb:100:in `block in handle_request'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/puma-6.4.2/lib/puma/thread_pool.rb:378:in `with_force_shutdown'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/puma-6.4.2/lib/puma/request.rb:99:in `handle_request'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/puma-6.4.2/lib/puma/server.rb:464:in `process_client'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/puma-6.4.2/lib/puma/server.rb:245:in `block in run'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/puma-6.4.2/lib/puma/thread_pool.rb:155:in `block in spawn_thread'
 2a1f0b55 | /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/logging-2.3.1/lib/logging/diagnostic_context.rb:474:in `block in create_with_logging_context'
2024-05-14T21:18:25 [I|app|2a1f0b55] Completed 500 Internal Server Error in 221ms (Views: 0.2ms | ActiveRecord: 6.6ms | Allocations: 16103)

@ianballou
Copy link
Member

I realize that we will definitely need some way to tell push repositories apart from normal ones. Relying on the version_href to say "container-push" in it could be a pretty good way to do so. When making content views, we'll need it to determine how contents should be copied. We'll also need it for deleting container push repositories, since it's the distribution that needs to be deleted rather than the repo itself.

@ianballou
Copy link
Member

Containers with tags such as default_organization/dummy_product/foo:bar end up losing the tag when pushed. I think this is okay since the tag is on the podman side; however, this means that users would technically need to pull using a different name than the name they pushed with. This may need further discussion since I could see users expecting either behavior (tag loss vs tag specification on pull).

I missed this comment before. The tag is separate from the repository name -- it is tagging the manifest that the user is pushing up. Under the hood, if not tag is noted, podman will auto-assign latest to the manifest being pushed. I'm guessing in this case then that all pushed content is getting the latest tag. It's important that tags are taken in exactly as they're pushed.

@ianballou
Copy link
Member

I tested passing in custom tags and it works :)

vagrant@centos9-katello-devel ~/foreman $ podman push 66640e9653de  `hostname`/default_organization/buttermilk_biscuits/busybox:tacos
Getting image source signatures
Copying blob 29e363cff31e skipped: already exists  
Copying config 66640e9653 done   | 
Writing manifest to image destination
vagrant@centos9-katello-devel ~/foreman $ sudo pulp show --href /pulp/api/v3/content/container/tags/?repository_version=/pulp/api/v3/repositories/container/container-push/018f9759-7d55-7b1e-82b0-f2f062ebd606/versions/1/
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "pulp_href": "/pulp/api/v3/content/container/tags/018f9759-8181-7602-b74e-db7c22a9981b/",
      "pulp_created": "2024-05-20T18:52:56.577888Z",
      "pulp_last_updated": "2024-05-20T18:52:56.577901Z",
      "name": "latest",
      "tagged_manifest": "/pulp/api/v3/content/container/manifests/018f9759-816b-7ed1-a0a9-6f9b8aabfbbc/"
    }
  ]
}

vagrant@centos9-katello-devel ~/foreman $ sudo pulp show --href /pulp/api/v3/content/container/tags/?repository_version=/pulp/api/v3/repositories/container/container-push/018f9759-7d55-7b1e-82b0-f2f062ebd606/versions/2/
{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "pulp_href": "/pulp/api/v3/content/container/tags/018f975c-858c-774c-9271-e67a92edcf5e/",
      "pulp_created": "2024-05-20T18:56:14.221447Z",
      "pulp_last_updated": "2024-05-20T18:56:14.221459Z",
      "name": "tacos",
      "tagged_manifest": "/pulp/api/v3/content/container/manifests/018f975c-857f-7709-9ab5-d8e897713f8e/"
    },
    {
      "pulp_href": "/pulp/api/v3/content/container/tags/018f9759-8181-7602-b74e-db7c22a9981b/",
      "pulp_created": "2024-05-20T18:52:56.577888Z",
      "pulp_last_updated": "2024-05-20T18:52:56.577901Z",
      "name": "latest",
      "tagged_manifest": "/pulp/api/v3/content/container/manifests/018f9759-816b-7ed1-a0a9-6f9b8aabfbbc/"
    }
  ]
}

@qcjames53
Copy link
Contributor Author

Containers with tags such as default_organization/dummy_product/foo:bar end up losing the tag when pushed. I think this is okay since the tag is on the podman side; however, this means that users would technically need to pull using a different name than the name they pushed with. This may need further discussion since I could see users expecting either behavior (tag loss vs tag specification on pull).

I missed this comment before. The tag is separate from the repository name -- it is tagging the manifest that the user is pushing up. Under the hood, if not tag is noted, podman will auto-assign latest to the manifest being pushed. I'm guessing in this case then that all pushed content is getting the latest tag. It's important that tags are taken in exactly as they're pushed.

ianballou and I talked about this outside of the PR and we came to the conclusion that we are handling tags correctly at the moment. Tag info shouldn't need to be stored anywhere in the repo; handling everything with Pulp should be completely fine.

Copy link
Member

@ianballou ianballou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird errors aside, pushing via name and via IDs are both creating repositories in the proper places 👍

@ianballou
Copy link
Member

ianballou commented May 20, 2024

As we discussed in-person before, the main thing missing now is being able to index content. To do so, I'd recommend a strategy like:

  1. Set up the container push repositories API for easy use by adding the following to app/services/katello/pulp3/api/docker.rb:
def container_push_repositories_api
  PulpContainerClient::RepositoriesContainerPushApi.new(api_client)
end
  1. Use the API to look up the Pulp repository's version_href after Pulp has taken in the proxied push request:
# ...
repo.update(version_href: ::Katello::Pulp3::Repository.api(SmartProxy.pulp_primary, ::Katello::Repository::DOCKER_TYPE).
  container_push_repositories_api.list(name: container_repo_name_here).results.first.latest_version_href)
# ...

(Determined via https://www.rubydoc.info/gems/pulp_container_client/2.20.0/PulpContainerClient/RepositoriesContainerPushApi)

  1. New model I forgot to mention before:

Create the ::Katello::Pulp3::RepositoryReference that would normally happen during the Pulp 3 repo creation. This is skipped because Pulp is making the repo for us.

          # pulp_repo_href looks like /pulp/api/v3/repositories/container/container-push/018f9759-7d55-7b1e-82b0-f2f062ebd606/
          # Note that there is no version -- this is just the repository reference, not a reference to any repository version
          RepositoryReference.where(
            root_repository_id: repo.root_id,
            content_view_id: repo.content_view.id,
            repository_href: pulp_api_response.pulp_href).create!
  1. Index the repository via repo.index_content.

Bonus points:

Using a similar method as above, also find the distribution and save it as a Katello::Pulp3::DistributionReference distribution reference record on the ::Katello::Repository

-> API: PulpContainerClient::DistributionsContainerApi

@qcjames53
Copy link
Contributor Author

@ianballou I really appreciate all the help with content indexing above (too big to quote). The newest update to the PR should fix the duplicate Pulp entry, set the root repo's version_href properly, and handle errors so that Podman can display them.

@ianballou
Copy link
Member

Already can see one improvement:

vagrant@centos9-katello-devel ~/katello $ podman push 66640e9653de  `hostname`/default_organization/buttermilk_biscuits/busybox:latest
Getting image source signatures
Copying blob b7d21803af9e [--------------------------------------] 8.0b / 3.0MiB | 1.3 MiB/s
WARN[0001] Failed, retrying in 1s ... (1/3). Error: writing blob: initiating layer upload to /v2/default_organization/buttermilk_biscuits/busybox/blobs/uploads/ in centos9-katello-devel.manicotto.example.com: name invalid: Product label 'buttermilk_biscuits' is ambiguous. Try using an id-based container name. 
Getting image source signatures
Copying blob b7d21803af9e [--------------------------------------] 8.0b / 3.0MiB | 1.8 MiB/s
WARN[0004] Failed, retrying in 1s ... (2/3). Error: writing blob: initiating layer upload to /v2/default_organization/buttermilk_biscuits/busybox/blobs/uploads/ in centos9-katello-devel.manicotto.example.com: name invalid: Product label 'buttermilk_biscuits' is ambiguous. Try using an id-based container name. 
Getting image source signatures
Copying blob b7d21803af9e [--------------------------------------] 8.0b / 3.0MiB | 2.0 MiB/s
WARN[0007] Failed, retrying in 1s ... (3/3). Error: writing blob: initiating layer upload to /v2/default_organization/buttermilk_biscuits/busybox/blobs/uploads/ in centos9-katello-devel.manicotto.example.com: name invalid: Product label 'buttermilk_biscuits' is ambiguous. Try using an id-based container name. 
Getting image source signatures
Copying blob b7d21803af9e [--------------------------------------] 8.0b / 3.0MiB | 1.7 MiB/s
Error: writing blob: initiating layer upload to /v2/default_organization/buttermilk_biscuits/busybox/blobs/uploads/ in centos9-katello-devel.manicotto.example.com: name invalid: Product label 'buttermilk_biscuits' is ambiguous. Try using an id-based container name.

) unless root_repo.present? && instance_repo.present?

api = ::Katello::Pulp3::Repository.api(SmartProxy.pulp_primary, ::Katello::Repository::DOCKER_TYPE).container_push_api
latest_version_href = api.list(name: @container_path_input).results.first.latest_version_href
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

api.list(name: @container_path_input).results.first was nil for me when pushing via the ID scheme for some reason.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is interesting. @container_path_input should be exactly what the user entered. I'll look into it.

@@ -71,7 +71,7 @@ class RootRepository < Katello::Model
validates_with Validators::ContainerImageNameValidator, :attributes => :docker_upstream_name, :allow_blank => true, :if => :docker?

validate :ensure_valid_docker_attributes, :if => :docker?
validate :ensure_docker_repo_unprotected, :if => :docker?
#validate :ensure_docker_repo_unprotected, :if => :docker?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to tomorrow Quinn: look into this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure we can completely remove the validation and the validation method.

@ianballou
Copy link
Member

One thing I noticed is that errors are not easily recovered from -- in one case my push succeeded, but the repository remained and I had to do some cleanup before trying to push again. We could potentially deal with it in a later PR, depends on if it ends up being a bigger issue.

@ianballou
Copy link
Member

ianballou commented May 22, 2024

I'm seeing some issues with bigger container images where the repository is created twice. I can reproduce the issue solidly with quay.io/curl/curl:latest

unprotected: true,
is_container_push: true
)
sync_task(::Actions::Katello::Repository::CreateRoot, root, @container_path_input)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found an interesting issue here -- there seems to be a race condition.

Pushing content is concurrent. That means that multiple threads could be in this if statement at once time. So, ::Actions::Katello::Repository::CreateRoot is being run even when @product.root_repositories.where(label: @container_name).empty? turns false.

You can reproduce this in the console by sticking a debugger right before the sync_task call. Multiple different debuggers will run at once due to the concurrency, and they'll all queue up before the sync_task call.

This is tricky because we need a way for one of the concurrent calls from podman to become the "chosen one" and for the rest of the calls to wait. One idea that comes to mind would be to implement some sort of lock that is saved in the database.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I confirmed that using a single-threaded development environment "solves" the issue, so concurrency does seem to be the problem.

Copy link
Member

@ianballou ianballou May 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One idea would be to use https://apidock.com/rails/v6.0.0/ActiveRecord/Relation/create_or_find_by

(or maybe create_or_find_by!)

I wonder if it would be easier to stop using the CreateRoot action and instead create the database entries manually. We'd then be able to remove the conditionals from the current actions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locking is potentially error-prone, so let's try that only as a last resort.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for inspiration, https://api.rubyonrails.org/v7.1.3.2/classes/ActiveRecord/Relation.html#method-i-find_or_initialize_by is also available -- it would allow to instantiate the RootRepository object OR actually get a record if it exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One idea would be to use https://apidock.com/rails/v6.0.0/ActiveRecord/Relation/create_or_find_by

(or maybe create_or_find_by!)

I wonder if it would be easier to stop using the CreateRoot action and instead create the database entries manually. We'd then be able to remove the conditionals from the current actions.

I've been experimenting with that for testing different approaches. I'm going to try and roll everything back into the foreman tasks unless that breaks concurrency, just so there isn't two different methods in Katello that do the same thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also thank you for the resources. Have been trying to get create_or_find_by to work but there are a lot of layers of foreman tasks to unroll.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of the newest push, I believe this is working correctly. I used create_or_find_by inside of app/models/katello/glue/pulp/repos.rb. When you have time, please confirm the issue is fixed on your end (I've been having trouble getting the endpoint to be hit simultaneously on my device).

@@ -85,6 +84,223 @@ def registry_authorize
return false
end

def handle_blob_push(props = nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's doing more than handling blobs -- maybe change the name to something like run_pre_push_checks ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up splitting this into two before_actions: blob_push_validation and create_container_repo_if_needed. I think this will also make the lock implementation cleaner. Does that sound ok to you?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm looking into if I can move the logic for testing if a container is needed into the before_action line

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point was more that it's also handling pre-checks for manifests, so having "blob" in the name was a little misleading.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does container_push_prop_validation sound?

Copy link
Member

@ianballou ianballou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finished up a functional test, was able to push the fedora-bootc image multiple times and the content all ended up in the same repository. It seems like the concurrency problem was solved, nice!


One issue I noticed -- pushing by IDs to a repository right after pushing by name created a different repository in Pulp. It didn't create another repository in Katello though, which is correct. As a result though, the content pushed by ID in Pulp appears to have disappeared in the Katello UI.

I was going to suggest translating the IDs to names before pushing content, but that would defeat the whole purpose of being able to differentiate the container names in Pulp.

To fix this, I think we'd need to know if a repository was originally pushed by name in order to decide which repo to push content to -- the "name" repo, or the "ID" repo.

If a user later creates an org / product that makes the naming ambiguous, they'll need to be able to keep pushing content to their original repositories that they pushed to by name. To do so, they'll need to start pushing by ID.

skip_before_action :authorize
before_action :optional_authorize, only: [:token, :catalog]
before_action :registry_authorize, except: [:token, :v1_search, :catalog]
before_action :authorize_repository_read, only: [:pull_manifest, :tags_list]
# TODO: authorize_repository_write commented out until Katello indexes Pulp container push repositories.
# before_action :authorize_repository_write, only: [:start_upload_blob, :upload_blob, :finish_upload_blob,
# :push_manifest]
# before_action :authorize_repository_write, only: [:start_upload_blob, :upload_blob, :finish_upload_blob, :push_manifest]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you if you want to leave this for a second PR, I know this one is already so large.

@qcjames53
Copy link
Contributor Author

qcjames53 commented May 30, 2024

@ianballou This newest push is very messy, sorry about that. I took one of your suggestions above and inverted it a little; pulp is now saving containers by id instead of name. I'm pretty sure there's a decent amount of work to do to fix this up on the container pull side, but push seems to work ok. What are your thoughts?

Also: still working on the test cases. I think you can see my paltry progress in this push.

@ianballou
Copy link
Member

I wonder if this is starting to get more complicated than we need, especially since this capitalization scenario we're guarding against should be very rare.

I'm trying to think if we could completely throw out the "by ID" style to ensure that Katello and Pulp are always aligned.

@ianballou
Copy link
Member

Throwing out the ID method entirely will force the rare ambiguous org / product owner to recreate the org / product. I think the safer middle ground would be to check if a user is trying to push content to a repository with a different name / ID scheme and to tell them to destroy it first. It makes for a decent experience for the very rare ambiguous org/product user, and shouldn't over-complicate the code for the 99% use-case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants