Skip to content
Open
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
1 change: 1 addition & 0 deletions image/cli/mascli/must-gather/mg-summary-db2u
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
echo "IBM Db2"
echo "--------------------------------------------------------------------------------"
oc -n db2u get db2ucluster -o jsonpath='{range .items[*]}{"Db2uCluster"}/{.metadata.name} = {.status.state}{"\n"}{end}'
oc -n db2u get db2uinstance -o jsonpath='{range .items[*]}{"Db2uCluster"}/{.metadata.name} = {.status.state}{"\n"}{end}'

exit 0
41 changes: 23 additions & 18 deletions python/src/mas/cli/update/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def update(self, argv):

self.printH2("Supported Dependency Updates")
if self.getParam("db2_namespace") != "":
self.printSummary("IBM Db2", f"All Db2uCluster instances in {self.getParam('db2_namespace')}")
self.printSummary("IBM Db2", f"All Db2uCluster and Db2uInstance instances in {self.getParam('db2_namespace')}")
else:
self.printSummary("IBM Db2", "No action required")

Expand Down Expand Up @@ -560,22 +560,25 @@ def detectDb2uOrKafka(self, mode: str) -> bool:
if mode == "db2":
haloStartingMessage = "Checking for Db2uCluster instances to update"
apiVersion = "db2u.databases.ibm.com/v1"
kind = "Db2uCluster"
kinds = ["Db2uCluster", "Db2uInstance"]
paramName = "db2_namespace"
elif mode == "kafka":
haloStartingMessage = "Checking for Kafka instances to update"
apiVersion = "kafka.strimzi.io/v1beta2"
kind = "Kafka"
kinds = ["Kafka"]
paramName = "kafka_namespace"
else:
self.fatalError("Unexpected error")

with Halo(text=haloStartingMessage, spinner=self.spinner) as h:
try:
k8sAPI = self.dynamicClient.resources.get(api_version=apiVersion, kind=kind)
instances = k8sAPI.get().to_dict()["items"]
instances = []
for kind in kinds:
k8sAPI = self.dynamicClient.resources.get(api_version=apiVersion, kind=kind)
instances.extend(k8sAPI.get().to_dict()["items"])
logger.debug(f"Found {len(instances)} {kind} instances on the cluster")

logger.debug(f"Found {len(instances)} {kind} instances on the cluster")
kindString = "/".join([kind + "s" for kind in kinds])
if len(instances) > 0:
# If the user provided the namespace using --db2-namespace then we don't have any work to do here
if self.getParam(paramName) == "":
Expand All @@ -585,30 +588,32 @@ def detectDb2uOrKafka(self, mode: str) -> bool:

if len(namespaces) == 1:
# If db2u is only in one namespace, we will update that
h.stop_and_persist(symbol=self.successIcon, text=f"{len(instances)} {kind}s ({apiVersion}) in namespace '{list(namespaces)[0]}' will be updated")
logger.debug(f"There is only one namespace containing {kind}s so we will target that one: {namespaces}")
h.stop_and_persist(symbol=self.successIcon, text=f"{len(instances)} {kindString} ({apiVersion}) in namespace '{list(namespaces)[0]}' will be updated")
logger.debug(f"There is only one namespace containing {kindString} so we will target that one: {namespaces}")
self.setParam(paramName, list(namespaces)[0])
elif self.noConfirm:
# If db2u is in multiple namespaces and user has disabled prompts then we must error
h.stop_and_persist(symbol=self.failureIcon, text=f"{len(instances)} {kind}s ({apiVersion}) were found in multiple namespaces")
logger.warning(f"There are multiple namespaces containing {kind}s and user has enable --no-confirm without setting --{mode}-namespace: {namespaces.keys()}")
self.fatalError(f"{kind}s are installed in multiple namespaces. You must instruct which one to update using the '--{mode}-namespace' argument")
namespaceList = ", ".join(list(namespaces))
h.stop_and_persist(symbol=self.failureIcon, text=f"{len(instances)} {kindString} ({apiVersion}) were found in multiple namespaces")
logger.warning(f"There are multiple namespaces containing {kindString} and user has enable --no-confirm without setting --{mode}-namespace: {namespaceList}")
self.fatalError(f"{kindString} are installed in multiple namespaces. You must instruct which one to update using the '--{mode}-namespace' argument")
else:
# Otherwise, provide user the list of namespaces we found and ask them to pick on
h.stop_and_persist(symbol=self.successIcon, text=f"{len(instances)} {kind}s ({apiVersion}) found in multiple namespaces")
logger.debug(f"There are multiple namespaces containing {kind}s, user must choose: {namespaces}")
h.stop_and_persist(symbol=self.successIcon, text=f"{len(instances)} {kindString} ({apiVersion}) found in multiple namespaces")
logger.debug(f"There are multiple namespaces containing {kindString}, user must choose: {namespaces}")
self.printDescription([
f"{kind}s were found in multiple namespaces, select the namespace to target from the list below:"
f"{kindString}s were found in multiple namespaces, select the namespace to target from the list below:"
])
for index, ns in enumerate(sorted(namespaces), start=1):
self.printDescription([f"{index}. {ns}"])
self.promptForListSelect("Select namespace", sorted(namespaces), paramName)
else:
logger.debug(f"Found no instances of {kind} to update")
h.stop_and_persist(symbol=self.successIcon, text=f"Found no {kind} ({apiVersion}) instances to update")
logger.debug(f"Found no instances of {kindString} to update")
h.stop_and_persist(symbol=self.successIcon, text=f"Found no {kindString} ({apiVersion}) instances to update")
except (ResourceNotFoundError, NotFoundError):
logger.debug(f"{kind}.{apiVersion} is not available in the cluster")
h.stop_and_persist(symbol=self.successIcon, text=f"{kind}.{apiVersion} is not available in the cluster")
kindString = ", ".join(kinds)
logger.debug(f"{'[' + kindString + ']'}.{apiVersion} is not available in the cluster")
h.stop_and_persist(symbol=self.successIcon, text=f"{kindString}.{apiVersion} is not available in the cluster")

# With Kafka we also have to determine the provider (strimzi or redhat)
if mode == "kafka" and self.getParam("kafka_namespace") != "" and self.getParam("kafka_provider") == "":
Expand Down
12 changes: 12 additions & 0 deletions python/test/update/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env python
# *****************************************************************************
# Copyright (c) 2026 IBM Corporation and other Contributors.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# *****************************************************************************

# Made with Bob
110 changes: 110 additions & 0 deletions python/test/update/test_db2u_interactive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env python
# *****************************************************************************
# Copyright (c) 2026 IBM Corporation and other Contributors.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# *****************************************************************************

from utils import UpdateTestConfig, run_update_test
import sys
import os
import pytest

# Add test directory to path for utils import
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))


@pytest.mark.parametrize("resource_kind", ["Db2uCluster", "Db2uInstance"])
def test_db2u_one_namespace(tmpdir, resource_kind):
"""Test interactive update when exactly one namespace contains Db2U resources."""

prompt_handlers = {
# Proceed with current cluster
'.*Proceed with this cluster?.*': lambda msg: 'y',
# Catalog selection
'.*Select catalog version.*': lambda msg: '1',
# Final confirmation
'.*Proceed with these settings.*': lambda msg: 'y',
}

config = UpdateTestConfig(
prompt_handlers=prompt_handlers,
installed_catalog_id="v9-251231-amd64",
target_catalog_version="v9-260129-amd64",
db2u_namespaces=["db2u-system"], # Single namespace
db2u_resource_kind=resource_kind,
mas_instances=[{
"metadata": {"name": "inst1"},
"status": {"versions": {"reconciled": "9.1.7"}}
}],
timeout_seconds=30
)

run_update_test(tmpdir, config)


@pytest.mark.parametrize("resource_kind", ["Db2uCluster", "Db2uInstance"])
def test_db2u_multiple_namespaces(tmpdir, resource_kind):
"""Test interactive update when multiple namespaces contain Db2U resources."""

prompt_handlers = {
# Proceed with current cluster
'.*Proceed with this cluster?.*': lambda msg: 'y',
# Catalog selection
'.*Select catalog version.*': lambda msg: '1',
# Namespace selection - user chooses second namespace
'.*Select namespace.*': lambda msg: '2',
# Final confirmation
'.*Proceed with these settings.*': lambda msg: 'y',
}

config = UpdateTestConfig(
prompt_handlers=prompt_handlers,
installed_catalog_id="v9-251231-amd64",
target_catalog_version="v9-260129-amd64",
db2u_namespaces=["db2u-ns1", "db2u-ns2", "db2u-ns3"], # Multiple namespaces
db2u_resource_kind=resource_kind,
mas_instances=[{
"metadata": {"name": "inst1"},
"status": {"versions": {"reconciled": "9.1.7"}}
}],
timeout_seconds=30
)

run_update_test(tmpdir, config)


@pytest.mark.parametrize("resource_kind", ["Db2uCluster", "Db2uInstance"])
def test_db2u_none_found(tmpdir, resource_kind):
"""Test interactive update when no Db2U resources exist."""

prompt_handlers = {
# Proceed with current cluster
'.*Proceed with this cluster?.*': lambda msg: 'y',
# Catalog selection
'.*Select catalog version.*': lambda msg: '1',
# Final confirmation
'.*Proceed with these settings.*': lambda msg: 'y',
}

config = UpdateTestConfig(
prompt_handlers=prompt_handlers,
installed_catalog_id="v9-251231-amd64",
target_catalog_version="v9-260129-amd64",
db2u_namespaces=[], # No Db2U resources
db2u_resource_kind=resource_kind,
mas_instances=[{
"metadata": {"name": "inst1"},
"status": {"versions": {"reconciled": "9.1.7"}}
}],
timeout_seconds=30
)

run_update_test(tmpdir, config)


# Made with Bob
173 changes: 173 additions & 0 deletions python/test/update/test_db2u_non_interactive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#!/usr/bin/env python
# *****************************************************************************
# Copyright (c) 2026 IBM Corporation and other Contributors.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# *****************************************************************************

from utils import UpdateTestConfig, run_update_test
import sys
import os
import pytest

# Add test directory to path for utils import
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))


@pytest.mark.parametrize("resource_kind", ["Db2uCluster", "Db2uInstance"])
def test_db2u_one_namespace_no_arg(tmpdir, resource_kind):
"""Test non-interactive update with one namespace and no --db2-namespace arg.

Expected behavior:
- Automatically detects and selects the single namespace
- Sets db2_namespace parameter
- No prompts (--no-confirm mode)
- Update proceeds successfully
"""

config = UpdateTestConfig(
prompt_handlers={}, # No prompts in non-interactive mode
installed_catalog_id="v9-251231-amd64",
target_catalog_version="v9-260129-amd64",
db2u_namespaces=["db2u-system"], # Single namespace
db2u_resource_kind=resource_kind,
mas_instances=[{
"metadata": {"name": "inst1"},
"status": {"versions": {"reconciled": "9.1.7"}}
}],
argv=['--catalog', 'v9-260129-amd64', '--no-confirm'],
timeout_seconds=30
)

run_update_test(tmpdir, config)


@pytest.mark.parametrize("resource_kind", ["Db2uCluster", "Db2uInstance"])
def test_db2u_one_namespace_with_arg(tmpdir, resource_kind):
"""Test non-interactive update with explicit --db2-namespace argument.

Expected behavior:
- Uses the explicitly provided namespace
- Sets db2_namespace parameter to provided value
- No namespace detection needed
- Update proceeds successfully
"""

config = UpdateTestConfig(
prompt_handlers={}, # No prompts in non-interactive mode
installed_catalog_id="v9-251231-amd64",
target_catalog_version="v9-260129-amd64",
db2u_namespaces=["db2u-system"], # Single namespace exists
db2u_resource_kind=resource_kind,
db2u_namespace_arg="db2u-system", # Explicit namespace argument
mas_instances=[{
"metadata": {"name": "inst1"},
"status": {"versions": {"reconciled": "9.1.7"}}
}],
argv=['--catalog', 'v9-260129-amd64', '--db2-namespace', 'db2u-system', '--no-confirm'],
timeout_seconds=30
)

run_update_test(tmpdir, config)


@pytest.mark.parametrize("resource_kind", ["Db2uCluster", "Db2uInstance"])
def test_db2u_multiple_namespaces_no_arg(tmpdir, resource_kind):
"""Test non-interactive update with multiple namespaces and no arg - should fail.

Expected behavior:
- Detects resources in multiple namespaces
- Displays failure message about multiple namespaces
- Raises SystemExit with non-zero exit code
- Error message indicates --db2-namespace argument is required
"""

config = UpdateTestConfig(
prompt_handlers={}, # No prompts in non-interactive mode
installed_catalog_id="v9-251231-amd64",
target_catalog_version="v9-260129-amd64",
db2u_namespaces=["db2u-ns1", "db2u-ns2", "db2u-ns3"], # Multiple namespaces
db2u_resource_kind=resource_kind,
mas_instances=[{
"metadata": {"name": "inst1"},
"status": {"versions": {"reconciled": "9.1.7"}}
}],
argv=['--catalog', 'v9-260129-amd64', '--no-confirm'],
expect_system_exit=True, # Expect failure
timeout_seconds=30
)

run_update_test(tmpdir, config)


@pytest.mark.parametrize("resource_kind", ["Db2uCluster", "Db2uInstance"])
def test_db2u_multiple_namespaces_with_arg(tmpdir, resource_kind):
"""Test non-interactive update with multiple namespaces but explicit arg.

Expected behavior:
- Uses the explicitly provided namespace (db2u-ns2)
- Ignores other namespaces with Db2U resources
- Sets db2_namespace parameter to provided value
- Update proceeds successfully
"""

config = UpdateTestConfig(
prompt_handlers={}, # No prompts in non-interactive mode
installed_catalog_id="v9-251231-amd64",
target_catalog_version="v9-260129-amd64",
db2u_namespaces=["db2u-ns1", "db2u-ns2", "db2u-ns3"], # Multiple namespaces
db2u_resource_kind=resource_kind,
db2u_namespace_arg="db2u-ns2", # Explicit namespace argument
mas_instances=[{
"metadata": {"name": "inst1"},
"status": {"versions": {"reconciled": "9.1.7"}}
}],
argv=['--catalog', 'v9-260129-amd64', '--db2-namespace', 'db2u-ns2', '--no-confirm'],
timeout_seconds=30
)

run_update_test(tmpdir, config)


@pytest.mark.parametrize("resource_kind,with_arg", [
("Db2uCluster", False),
("Db2uCluster", True),
("Db2uInstance", False),
("Db2uInstance", True),
])
def test_db2u_no_namespaces(tmpdir, resource_kind, with_arg):
"""Test non-interactive update when no Db2U resources found.

Expected behavior:
- Displays message that no resources were found
- db2_namespace parameter remains empty
- Update continues without error (not a failure condition)
"""

argv = ['--catalog', 'v9-260129-amd64', '--no-confirm']
if with_arg:
argv.extend(['--db2-namespace', 'db2u-system'])

config = UpdateTestConfig(
prompt_handlers={}, # No prompts in non-interactive mode
installed_catalog_id="v9-251231-amd64",
target_catalog_version="v9-260129-amd64",
db2u_namespaces=[], # No resources
db2u_resource_kind=resource_kind,
db2u_namespace_arg="db2u-system" if with_arg else None,
mas_instances=[{
"metadata": {"name": "inst1"},
"status": {"versions": {"reconciled": "9.1.7"}}
}],
argv=argv,
timeout_seconds=30
)

run_update_test(tmpdir, config)


# Made with Bob
Loading