Skip to content

Adds model behavior for RegistryUpstream

What does this MR do and why?

Adds model behavior for RegistryUpstream in Virtual Registry for containers

This is part of a series of MRs for #548783 (closed):

References

Screenshots or screen recordings

NA

Database Review

RegistryUpstream.sync_higher_positions

We can have 5 docker registries max.

Each registry can be connected to 5 upstreams max.

So the worst case scenario here would be max 25 (5x5)

These are empty tables in production. Here's the EXPLAIN ANALYZE output from GDK.

SQL
EXPLAIN UPDATE virtual_registries_container_registry_upstreams
SET
    position = position - 1
WHERE
    virtual_registries_container_registry_upstreams.id IN (
        SELECT
            virtual_registries_container_registry_upstreams.id
        FROM
            virtual_registries_container_registry_upstreams
            JOIN (
                SELECT
                    virtual_registries_container_registry_upstreams.registry_id,
                    virtual_registries_container_registry_upstreams.position
                FROM
                    virtual_registries_container_registry_upstreams
                WHERE
                    virtual_registries_container_registry_upstreams.registry_id = 5
                ORDER BY
                    virtual_registries_container_registry_upstreams.position ASC
            ) AS subquery ON virtual_registries_container_registry_upstreams.registry_id = subquery.registry_id
        WHERE
            virtual_registries_container_registry_upstreams.position > subquery.position
    );
Query plan
 Update on virtual_registries_container_registry_upstreams  (cost=15.53..17.14 rows=0 width=0)
   ->  Nested Loop  (cost=15.53..17.14 rows=9 width=48)
         ->  HashAggregate  (cost=15.37..15.46 rows=9 width=48)
               Group Key: virtual_registries_container_registry_upstreams_1.id
               ->  Nested Loop  (cost=0.30..15.35 rows=9 width=48)
                     ->  Subquery Scan on subquery  (cost=0.15..5.29 rows=5 width=44)
                           ->  Index Only Scan using constraint_vreg_container_reg_upst_on_unique_reg_pos on virtual_registries_container_registry_upstreams virtual_registries_container_registry_upstreams_2  (cost=0.15..5.24 rows=5 width=10)
                                 Index Cond: (registry_id = 5)
                     ->  Index Scan using constraint_vreg_container_reg_upst_on_unique_reg_pos on virtual_registries_container_registry_upstreams virtual_registries_container_registry_upstreams_1  (cost=0.15..1.99 rows=2 width=24)
                           Index Cond: ((registry_id = subquery.registry_id) AND ("position" > subquery."position"))
         ->  Index Scan using virtual_registries_container_registry_upstreams_pkey on virtual_registries_container_registry_upstreams  (cost=0.15..0.18 rows=1 width=16)
               Index Cond: (id = virtual_registries_container_registry_upstreams_1.id)
RegistryUpstream#sync_higher_positions

We can have 5 docker registries max.

Each registry can be connected to 5 upstreams max.

So the worst case scenario here would be max 25 (5x5)

These are empty tables in production. Here's the EXPLAIN ANALYZE output from GDK.

SQL
EXPLAIN
UPDATE virtual_registries_container_registry_upstreams
SET
    position = position - 1
WHERE
    virtual_registries_container_registry_upstreams.registry_id = 5 AND
    virtual_registries_container_registry_upstreams.position >= 5;
Query plan
 Update on virtual_registries_container_registry_upstreams  (cost=0.15..3.20 rows=0 width=0)
   ->  Index Scan using constraint_vreg_container_reg_upst_on_unique_reg_pos on virtual_registries_container_registry_upstreams  (cost=0.15..3.20 rows=2 width=8)
         Index Cond: ((registry_id = 5) AND ("position" >= 5))
RegistryUpstream#update_position

We can have 5 docker registries max.

Each registry can be connected to 5 upstreams max.

So the worst case scenario here would be max 25 (5x5)

These are empty tables in production. Here's the EXPLAIN ANALYZE output from GDK.

SQL
EXPLAIN
UPDATE virtual_registries_container_registry_upstreams
SET
    position = CASE
        WHEN virtual_registries_container_registry_upstreams.id = 5 THEN 1
        WHEN virtual_registries_container_registry_upstreams.position = 1 THEN virtual_registries_container_registry_upstreams.position + 1
        ELSE virtual_registries_container_registry_upstreams.position
    END
WHERE
    virtual_registries_container_registry_upstreams.registry_id = 5;
Query plan
 Update on virtual_registries_container_registry_upstreams  (cost=0.15..5.30 rows=0 width=0)
   ->  Index Scan using constraint_vreg_container_reg_upst_on_unique_reg_pos on virtual_registries_container_registry_upstreams  (cost=0.15..5.30 rows=5 width=8)
         Index Cond: (registry_id = 5)

How to set up and validate locally

The only way to test is from the Rails console

1️⃣ Registry model

# get a root group
root_group = Group.first

# make sure that it's a root one
root_group.root?
=> true

# create a subgroup
subgroup = FactoryBot.create(:group, parent: root_group, name: 'test-subgroup')

# alternative to previous step, if you already created test-subgroup
subgroup = Namespace.where(parent: root_group, name: 'test-subgroup').first

# The registry object is the entry point of the feature and can only be linked with a root group.
# Let's try to create a registry on the subgroup:

r = ::VirtualRegistries::Container::Registry.create!(group: subgroup, name: subgroup.name)
ActiveRecord::RecordInvalid: Validation failed: Group must be a top level Group

# Create a registry on the root group:
r = ::VirtualRegistries::Container::Registry.create!(group: root_group, name: root_group.name)
=> #<VirtualRegistries::Container::Registry:0x00000001200f5f20 id: ....>

# Registry name should be unique within a group:
::VirtualRegistries::Container::Registry.create!(group: root_group, name: root_group.name)
ActiveRecord::RecordInvalid: Validation failed: Group has already been taken

2️⃣ Upstream model

# Given that the association with the registry is handled in a join table, an upstream can be created without pointing to a registry

# Let's check some of its validations
# name and credentials should be set
my_google_registry_url = "https://us-central1-docker.pkg.dev/my-project-id/my-repo/my-app:latest"
u = ::VirtualRegistries::Container::Upstream.create!(group: root_group, url: my_google_registry_url)
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank, Credentials must be a valid json schema

u_name = 'my-project-registry'
username = 'batman'
password = 'askalfred'
u = ::VirtualRegistries::Container::Upstream.create!(group: root_group, name: u_name, username: username, password: password, url: my_google_registry_url)

# Let's check some of its validations
# url should be correctly formatted
u.update!(url: "test")
ActiveRecord::RecordInvalid: Validation failed: Url is blocked: Only allowed schemes are http, https

u.reload

# credentials should both be set
u.update!(username: '', password: 'newpassword')
ActiveRecord::RecordInvalid: Validation failed: Username can't be blank

u.update!(username: 'newbatman', password: 'newpassword')
=> true

3️⃣ RegistryUpstream model

ru = ::VirtualRegistries::Container::RegistryUpstream.create!(group: root_group, registry: r, upstream: u)
=> #<VirtualRegistries::Container::RegistryUpstream:0x000000011cdd8340

# We don't allow multiple upstreams for the same registry
::VirtualRegistries::Container::RegistryUpstream.create!(group: root_group, registry: r, upstream: u)
ActiveRecord::RecordInvalid: Validation failed: Upstream has already been taken

Model navigation

r.upstreams.first
=> #<VirtualRegistries::Container::Upstream:0x0000000169731b98

u.registries.first
=> #<VirtualRegistries::Container::Registry:0x000000016967a650

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Related to #548783 (closed)

Edited by Radamanthus Batnag

Merge request reports

Loading