Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# Test artifacts
test/pylib/
test/test.cfg
test/nms/

# Build artifacts
*.egg-info/
dist/
build/

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@
KBase core service to manage app and module information, registration, and release.
Administrators need to be set separately for the job stats page by being added to [deploy.cfg.](https://github.com/kbaseapps/kb_Metrics/blob/master/deploy.cfg)

## Docker Registry Configuration

The catalog supports dual Docker registry configuration for Kubernetes deployments:

- **`docker_registry_host`** - Registry for internal operations (push/pull within cluster)
- **`docker_registry_client_host`** - Registry URL for client-facing image names (external access)

When only `docker_registry_host` is set, it is used for both internal operations and client references, maintaining backward compatibility.

Example configuration:
```bash
# Internal cluster registry
export docker_registry_host="internal-registry.cluster.local:5000"
# External registry URL for clients
export docker_registry_client_host="registry.example.com"
```

Test: Please refer to the instructions at the top of `test/test.cfg.example` file.

Build status:
Expand Down
4 changes: 3 additions & 1 deletion deployment/conf/.templates/deploy.cfg.templ
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ auth-service-api = {{ default .Env.kbase_endpoint "https://ci.kbase.us/services"
# Path to docker socket/host
docker-base-url = {{ default .Env.docker_base_url "unix://var/run/docker.sock" }}
# for tcp: "tcp://host:port"
# Docker registry to use
# Docker registry to use for internal operations (push/pull)
docker-registry-host = {{ default .Env.docker_registry_host "dockerhub-ci.kbase.us" }}
# Docker registry host for client-facing image names (defaults to docker-registry-host if not set)
docker-registry-client-host = {{ default .Env.docker_registry_client_host (default .Env.docker_registry_host "dockerhub-ci.kbase.us") }}

# Roles that can review/approve/disable modules and releases.
# Multiple admin roles can be specified as a comma delimited list
Expand Down
13 changes: 10 additions & 3 deletions lib/biokbase/catalog/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ def __init__(self, config):
self.docker_registry_host = config['docker-registry-host']
print('Docker registry host config = ' + self.docker_registry_host)

# Use client-specific registry host if defined, otherwise fall back to main registry host
if 'docker-registry-client-host' in config:
self.docker_registry_client_host = config['docker-registry-client-host']
else:
self.docker_registry_client_host = self.docker_registry_host
print('Docker registry client host config = ' + self.docker_registry_client_host)

if 'docker-push-allow-insecure' in config:
print('WARNING!! Docker docker-push-allow-insecure found in configuration. '
'This is no longer supported - use --insecure-registry on dockerd')
Expand Down Expand Up @@ -213,7 +220,7 @@ def register_repo(self, params, username, token):
t = threading.Thread(target=_start_registration, args=(
params, registration_id, timestamp, username, self.is_admin(username, token), token, self.db,
self.temp_dir, self.docker_base_url,
self.docker_registry_host, self.nms_url, self.nms_token, module_details,
self.docker_registry_host, self.docker_registry_client_host, self.nms_url, self.nms_token, module_details,
self.ref_data_base, self.kbase_endpoint,
prev_dev_version))
t.start()
Expand Down Expand Up @@ -1577,10 +1584,10 @@ def get_secure_config_params(self, username, token, params):

# NOT PART OF CLASS CATALOG!!
def _start_registration(params, registration_id, timestamp, username, is_admin, token, db,
temp_dir, docker_base_url, docker_registry_host, nms_url, nms_admin_token,
temp_dir, docker_base_url, docker_registry_host, docker_registry_client_host, nms_url, nms_admin_token,
module_details, ref_data_base, kbase_endpoint, prev_dev_version):
registrar = Registrar(params, registration_id, timestamp, username, is_admin, token, db,
temp_dir, docker_base_url, docker_registry_host, nms_url,
temp_dir, docker_base_url, docker_registry_host, docker_registry_client_host, nms_url,
nms_admin_token, module_details, ref_data_base, kbase_endpoint,
prev_dev_version)
registrar.start_registration()
18 changes: 15 additions & 3 deletions lib/biokbase/catalog/registrar.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Registrar:
# params is passed in from the controller, should be the same as passed into the spec
# db is a reference to the Catalog DB interface (usually a MongoCatalogDBI instance)
def __init__(self, params, registration_id, timestamp, username, is_admin, token, db, temp_dir,
docker_base_url, docker_registry_host, nms_url, nms_admin_token, module_details,
docker_base_url, docker_registry_host, docker_registry_client_host, nms_url, nms_admin_token, module_details,
ref_data_base, kbase_endpoint, prev_dev_version):
self.db = db
self.params = params
Expand All @@ -40,6 +40,7 @@ def __init__(self, params, registration_id, timestamp, username, is_admin, token
self.temp_dir = temp_dir
self.docker_base_url = docker_base_url
self.docker_registry_host = docker_registry_host
self.docker_registry_client_host = docker_registry_client_host

self.nms_url = nms_url

Expand Down Expand Up @@ -124,7 +125,11 @@ def start_registration(self):
# perhaps make this a self attr?
module_name_lc = self.get_required_field_as_string(self.kb_yaml,
'module-name').strip().lower()
self.image_name = self.docker_registry_host + '/kbase:' + module_name_lc + '.' + str(
# Use client registry host for image name that will be exposed to clients
self.image_name = self.docker_registry_client_host + '/kbase:' + module_name_lc + '.' + str(
git_commit_hash)
# Use internal registry host for docker operations (push, etc.)
self.internal_image_name = self.docker_registry_host + '/kbase:' + module_name_lc + '.' + str(
git_commit_hash)
ref_data_folder = None
ref_data_ver = None
Expand Down Expand Up @@ -212,7 +217,14 @@ def start_registration(self):
self.log('Report complete')

self.set_build_step('pushing docker image to registry')
self.push_docker_image(dockerclient, self.image_name)
# Push to the internal registry if different from client registry
if self.docker_registry_host != self.docker_registry_client_host:
self.log(f'Tagging image for internal registry: {self.internal_image_name}')
# Tag the image with the internal registry name
dockerclient.tag(self.image_name, self.internal_image_name)
self.push_docker_image(dockerclient, self.internal_image_name)
else:
self.push_docker_image(dockerclient, self.image_name)


else:
Expand Down
15 changes: 15 additions & 0 deletions test/catalog_config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ def test_catalog_with_retryWrites_is_true(self):
catalog = Catalog(self.catalog_cfg)
self.assertTrue(catalog.cc.db.mongo_retry_writes)

def test_docker_registry_host_configuration(self):
# Test with only docker-registry-host set (backward compatibility)
config = self.catalog_cfg.copy()
catalog = Catalog(config)
self.assertEqual(catalog.cc.docker_registry_host, config['docker-registry-host'])
self.assertEqual(catalog.cc.docker_registry_client_host, config['docker-registry-host'])

def test_docker_registry_client_host_configuration(self):
# Test with both registry hosts set
config = self.catalog_cfg.copy()
config['docker-registry-client-host'] = 'external.example.com'
catalog = Catalog(config)
self.assertEqual(catalog.cc.docker_registry_host, config['docker-registry-host'])
self.assertEqual(catalog.cc.docker_registry_client_host, 'external.example.com')

@classmethod
def setUpClass(cls):
print("++++++++++++ RUNNING catalog_config_test.py +++++++++++")
Expand Down
105 changes: 105 additions & 0 deletions test/registrar_registry_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import unittest
from unittest.mock import Mock, patch
import os

from biokbase.catalog.registrar import Registrar


class RegistrarRegistryTest(unittest.TestCase):

def setUp(self):
self.params = {'git_url': 'https://github.com/example/repo.git'}
self.registration_id = 'test123'
self.timestamp = 1234567890
self.username = 'testuser'
self.is_admin = False
self.token = 'test-token'
self.db = Mock()
self.temp_dir = '/tmp'
self.docker_base_url = 'unix://var/run/docker.sock'
self.nms_url = 'http://localhost:7125/rpc'
self.nms_admin_token = 'nms-token'
self.module_details = {'release_version_list': [], 'beta_version_list': []}
self.ref_data_base = '/kb/data'
self.kbase_endpoint = 'https://ci.kbase.us/services'
self.prev_dev_version = None

def test_registrar_with_same_registry_hosts(self):
"""Test registrar when both registry hosts are the same"""
docker_registry_host = 'registry.com'
docker_registry_client_host = 'registry.com'

registrar = Registrar(
self.params, self.registration_id, self.timestamp, self.username,
self.is_admin, self.token, self.db, self.temp_dir,
self.docker_base_url, docker_registry_host, docker_registry_client_host,
self.nms_url, self.nms_admin_token, self.module_details,
self.ref_data_base, self.kbase_endpoint, self.prev_dev_version
)

self.assertEqual(registrar.docker_registry_host, 'registry.com')
self.assertEqual(registrar.docker_registry_client_host, 'registry.com')

def test_registrar_with_different_registry_hosts(self):
"""Test registrar when registry hosts are different"""
docker_registry_host = 'internal.registry.com'
docker_registry_client_host = 'external.registry.com'

registrar = Registrar(
self.params, self.registration_id, self.timestamp, self.username,
self.is_admin, self.token, self.db, self.temp_dir,
self.docker_base_url, docker_registry_host, docker_registry_client_host,
self.nms_url, self.nms_admin_token, self.module_details,
self.ref_data_base, self.kbase_endpoint, self.prev_dev_version
)

self.assertEqual(registrar.docker_registry_host, 'internal.registry.com')
self.assertEqual(registrar.docker_registry_client_host, 'external.registry.com')

@patch('biokbase.catalog.registrar.subprocess')
@patch('biokbase.catalog.registrar.os.path.isdir')
@patch('biokbase.catalog.registrar.os.mkdir')
@patch('biokbase.catalog.registrar.codecs.open')
def test_image_name_construction(self, mock_open, mock_mkdir, mock_isdir, mock_subprocess):
"""Test that image names are constructed with correct registry hosts"""
mock_isdir.return_value = True
mock_subprocess.check_call.return_value = None
mock_subprocess.check_output.return_value = b'abcd1234'

# Mock file operations
mock_file = Mock()
mock_open.return_value.__enter__.return_value = mock_file

docker_registry_host = 'internal.registry.com'
docker_registry_client_host = 'external.registry.com'

registrar = Registrar(
self.params, self.registration_id, self.timestamp, self.username,
self.is_admin, self.token, self.db, self.temp_dir,
self.docker_base_url, docker_registry_host, docker_registry_client_host,
self.nms_url, self.nms_admin_token, self.module_details,
self.ref_data_base, self.kbase_endpoint, self.prev_dev_version
)

# Mock the kb_yaml content for module name
registrar.kb_yaml = {'module-name': 'TestModule'}
registrar.get_required_field_as_string = Mock(return_value='TestModule')

# Test that image names are constructed correctly
module_name_lc = 'testmodule'
git_commit_hash = 'abcd1234'

# These should be set during image name construction
expected_client_image = f'{docker_registry_client_host}/kbase:{module_name_lc}.{git_commit_hash}'
expected_internal_image = f'{docker_registry_host}/kbase:{module_name_lc}.{git_commit_hash}'

# Set the values as they would be in start_registration
registrar.image_name = expected_client_image
registrar.internal_image_name = expected_internal_image

self.assertEqual(registrar.image_name, expected_client_image)
self.assertEqual(registrar.internal_image_name, expected_internal_image)


if __name__ == '__main__':
unittest.main()
57 changes: 57 additions & 0 deletions test/registry_config_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import unittest
from unittest.mock import patch

from biokbase.catalog.controller import CatalogController


class RegistryConfigTest(unittest.TestCase):

def get_base_config(self):
return {
'mongodb-host': 'localhost:27017',
'mongodb-database': 'test',
'temp-dir': '/tmp',
'docker-base-url': 'unix://var/run/docker.sock',
'ref-data-base': '/kb/data',
'kbase-endpoint': 'https://ci.kbase.us/services',
'auth-service-api': 'http://localhost:7777',
'admin-roles': 'KBASE_ADMIN,CATALOG_ADMIN',
'nms-url': 'http://localhost:7125/rpc',
'nms-admin-token': 'test-token'
}

def test_docker_registry_host_only(self):
"""Test backward compatibility with only docker-registry-host set"""
config = self.get_base_config()
config['docker-registry-host'] = 'internal.registry.com'

with patch('biokbase.catalog.controller.MongoCatalogDBI'):
controller = CatalogController(config)
self.assertEqual(controller.docker_registry_host, 'internal.registry.com')
self.assertEqual(controller.docker_registry_client_host, 'internal.registry.com')

def test_docker_registry_client_host_set(self):
"""Test with both registry hosts set to different values"""
config = self.get_base_config()
config['docker-registry-host'] = 'internal.registry.com'
config['docker-registry-client-host'] = 'external.registry.com'

with patch('biokbase.catalog.controller.MongoCatalogDBI'):
controller = CatalogController(config)
self.assertEqual(controller.docker_registry_host, 'internal.registry.com')
self.assertEqual(controller.docker_registry_client_host, 'external.registry.com')

def test_docker_registry_client_host_same_as_host(self):
"""Test with both registry hosts set to same value"""
config = self.get_base_config()
config['docker-registry-host'] = 'registry.com'
config['docker-registry-client-host'] = 'registry.com'

with patch('biokbase.catalog.controller.MongoCatalogDBI'):
controller = CatalogController(config)
self.assertEqual(controller.docker_registry_host, 'registry.com')
self.assertEqual(controller.docker_registry_client_host, 'registry.com')


if __name__ == '__main__':
unittest.main()
5 changes: 4 additions & 1 deletion test/test.cfg.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ test-module-repo-2 = https://github.com/kbaseIncubator/catalog_test_module.git
# host where mongo lives, e.g. localhost:27017, for tests there should be not authentication required
mongodb-host = localhost:27017

# docker registry host endpoint
# docker registry host endpoint for internal operations
docker-registry-host = localhost:5000

# docker registry host endpoint for client-facing image names (optional, defaults to docker-registry-host)
# docker-registry-client-host = registry.example.com

# name of the mongo database
# WARNING: this will get wiped! Don't run tests against something that you may want to keep!
mongodb-database = catalog-test
Expand Down
Loading