-
Notifications
You must be signed in to change notification settings - Fork 59
[patch] Add standalone Db2uCluster to Db2uInstance migration pipeline and CLI command #2428
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
Draft
jainyjoseph
wants to merge
25
commits into
master
Choose a base branch
from
mascore-11096
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
1fcebca
[patch] Changes to support db migrate
jainyjoseph 5e5c402
Pre-commit updates
jainyjoseph b867311
[patch] Register command for Migrate
jainyjoseph c21895b
[patch] Updated pipelines
jainyjoseph 1deb3b9
[patch] Updated comments
jainyjoseph ce23d4b
[patch] Updated parameters
jainyjoseph 2daa20f
[patch] Updated parameters
jainyjoseph 8e2c8cc
[patch] Updated parameters to test
jainyjoseph d8397a6
Updated to add new case for db migration
jainyjoseph ba11151
Fixed the format
jainyjoseph fb9533a
Added the help text
jainyjoseph aeda580
Reverting self.version to default to test in containerised cli
jainyjoseph aa94523
Removed import client from app.py
jainyjoseph 84251ff
Removed unwanted lines from app.py
jainyjoseph 536d159
Fixed import modules in cli.py
jainyjoseph 4db262b
Merge branch 'master' into mascore-11096
jainyjoseph 8bb7022
merged master
jainyjoseph 20a83ad
updated python
jainyjoseph 1a7d8eb
Merge branch 'master' into mascore-11096
jainyjoseph 7c5db2e
Updated branch
jainyjoseph a2b7fc6
Updated branch #1
jainyjoseph a3d9fbc
Merge branch 'master' into mascore-11096
jainyjoseph 5408920
Rebuilding CLI
jainyjoseph 15e7b51
Updated the cli tag to test, revert after testing
jainyjoseph 0e45321
Reverting cli-base
jainyjoseph File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # ----------------------------------------------------------- | ||
| # Licensed Materials - Property of IBM | ||
| # 5737-M66 | ||
| # (C) Copyright IBM Corp. 2026 All Rights Reserved. | ||
| # US Government Users Restricted Rights - Use, duplication, or disclosure | ||
| # restricted by GSA ADP Schedule Contract with IBM Corp. | ||
| # ----------------------------------------------------------- | ||
|
|
||
| from ..cli import BaseApp # noqa: F401 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| # ----------------------------------------------------------- | ||
| # Licensed Materials - Property of IBM | ||
| # 5737-M66 | ||
| # (C) Copyright IBM Corp. 2026 All Rights Reserved. | ||
| # US Government Users Restricted Rights - Use, duplication, or disclosure | ||
| # restricted by GSA ADP Schedule Contract with IBM Corp. | ||
| # ----------------------------------------------------------- | ||
|
|
||
| import logging | ||
|
|
||
| from typing import List, Dict, Any | ||
| from halo import Halo | ||
| from prompt_toolkit import print_formatted_text, HTML | ||
| from openshift.dynamic.exceptions import NotFoundError | ||
|
|
||
| from ..cli import BaseApp | ||
| from .argParser import db2MigrationArgParser | ||
| from mas.devops.ocp import createNamespace | ||
| from mas.devops.tekton import preparePipelinesNamespace, installOpenShiftPipelines, updateTektonDefinitions, launchDb2MigrationPipeline | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class Db2MigrationApp(BaseApp): | ||
| """Application class for DB2 cluster migration""" | ||
|
|
||
| def detectDb2uClusters(self, namespace: str) -> List[Dict[str, Any]]: | ||
| """Detect all Db2uCluster instances in the specified namespace. | ||
|
|
||
| Args: | ||
| namespace (str): Kubernetes namespace to search | ||
|
|
||
| Returns: | ||
| List[Dict[str, Any]]: List of Db2uCluster resources found | ||
| """ | ||
| try: | ||
| db2ClusterAPI = self.dynamicClient.resources.get(api_version="db2u.databases.ibm.com/v1", kind="Db2uCluster") | ||
| clusters = db2ClusterAPI.get(namespace=namespace) | ||
| return clusters.items if clusters else [] | ||
| except NotFoundError: | ||
| return [] | ||
|
|
||
| def promptForCluster(self, clusters: List[Dict[str, Any]]) -> Dict[str, Any]: | ||
| """Prompt user to select a cluster from the detected list. | ||
|
|
||
| Args: | ||
| clusters (List[Dict[str, Any]]): List of available clusters | ||
|
|
||
| Returns: | ||
| Dict[str, Any]: Selected cluster resource | ||
| """ | ||
| if len(clusters) == 1: | ||
| cluster = clusters[0] | ||
| clusterName = cluster.metadata.name | ||
| self.printHighlight(f"Found 1 Db2uCluster: {clusterName}") | ||
| return cluster | ||
|
|
||
| # Multiple clusters - Prompt for selection | ||
| # self.printH2("Available Db2uClusters") | ||
| # options = [] | ||
| # for i, cluster in enumerate(clusters): | ||
| # name = cluster.metadata.name | ||
| # version = cluster.spec.version if hasattr(cluster.spec, "version") else "unknown" | ||
| # status = cluster.status.state if hasattr(cluster, "status") and hasattr(cluster.status, "state") else "unknown" | ||
| # options.append(f"{name} (version: {version}, status: {status})") | ||
|
|
||
| # selectedIndex = self.promptForListSelect("Select cluster to migrate", options) | ||
| # return clusters[selectedIndex] | ||
|
|
||
| self.printH2("Available Db2uClusters") | ||
| for i, cluster in enumerate(clusters): | ||
| name = cluster.metadata.name | ||
| version = cluster.spec.version if hasattr(cluster.spec, "version") else "unknown" | ||
| status = cluster.status.state if hasattr(cluster, "status") and hasattr(cluster.status, "state") else "unknown" | ||
| print(f" {i+1}. {name} (version: {version}, status: {status})") | ||
|
|
||
| selectedIndex = self.promptForInt("Select cluster to migrate", min=1, max=len(clusters)) | ||
| return clusters[selectedIndex - 1] | ||
|
|
||
| def promptForBackup(self) -> bool: | ||
| """Prompt user whether to perform backup before migration. | ||
|
|
||
| Returns: | ||
| bool: True if backup should be performed, False otherwise | ||
| """ | ||
| self.printH2("Backup Configuration") | ||
| print_formatted_text( | ||
| HTML( | ||
| "<Yellow>It is strongly recommended to backup before migration.</Yellow>\n" | ||
| "This will create a full database backup that can be used for rollback.\n" | ||
| ) | ||
| ) | ||
| return self.yesOrNo("Perform backup before migration") | ||
|
|
||
| def migrate(self, argv: List[str]) -> None: | ||
| """Main entry point for DB2 migration command. | ||
|
|
||
| Args: | ||
| argv (List[str]): Command line arguments | ||
| """ | ||
| args = db2MigrationArgParser.parse_args(argv) | ||
| self.noConfirm = args.no_confirm | ||
|
|
||
| # Connect to cluster | ||
| self.connect() | ||
|
|
||
| # Determine mode: interactive vs non-interactive | ||
| isInteractive = args.namespace is None | ||
|
|
||
| if isInteractive: | ||
| # Interactive mode | ||
| self.printH1("DB2 Cluster Migration") | ||
|
|
||
| # List db2u namespaces | ||
| with Halo(text="Detecting db2u namespaces", spinner=self.spinner) as h: | ||
| try: | ||
| namespaceAPI = self.dynamicClient.resources.get(api_version="v1", kind="Namespace") | ||
| allNamespaces = namespaceAPI.get() | ||
| db2uNamespaces = [ns.metadata.name for ns in allNamespaces.items if ns.metadata.name.startswith("db2u")] | ||
| # v1 = client.CoreV1Api() | ||
| # allNamespaces = v1.list_namespace() | ||
| # db2uNamespaces = [ns.metadata.name for ns in allNamespaces.items if ns.metadata.name.startswith("db2u")] | ||
|
|
||
| if db2uNamespaces: | ||
| h.succeed(f"Found {len(db2uNamespaces)} db2u namespace(s)") | ||
| print_formatted_text(HTML("<ansicyan>Available db2u namespaces:</ansicyan>")) | ||
| for ns in sorted(db2uNamespaces): | ||
| print(f" - {ns}") | ||
| print() | ||
| else: | ||
| h.info("No db2u namespaces found") | ||
| except Exception as e: | ||
| h.fail(f"Failed to list namespaces: {e}") | ||
|
|
||
| # Prompt for namespace with default | ||
| namespace = self.promptForString("Enter namespace containing Db2uClusters", default="db2u") | ||
|
|
||
| # Detect clusters | ||
| with Halo(text=f"Detecting Db2uClusters in namespace {namespace}", spinner=self.spinner) as h: | ||
| clusters = self.detectDb2uClusters(namespace) | ||
| if not clusters: | ||
| h.fail(f"No Db2uClusters found in namespace {namespace}") | ||
| self.fatalError(f"No Db2uClusters found in namespace {namespace}") | ||
| h.succeed(f"Found {len(clusters)} Db2uCluster(s)") | ||
|
|
||
| # Select cluster | ||
| selectedCluster = self.promptForCluster(clusters) | ||
| clusterName = selectedCluster.metadata.name | ||
|
|
||
| # Prompt for backup | ||
| enableBackup = self.promptForBackup() | ||
|
|
||
| else: | ||
| # Non-interactive mode | ||
| namespace = args.namespace | ||
| clusterName = args.cluster_name | ||
| enableBackup = args.backup == "true" if args.backup else True | ||
|
|
||
| # Validate cluster exists if name provided | ||
| if clusterName: | ||
| clusters = self.detectDb2uClusters(namespace) | ||
| clusterNames = [c.metadata.name for c in clusters] | ||
| if clusterName not in clusterNames: | ||
| self.fatalError(f"Cluster {clusterName} not found in namespace {namespace}") | ||
| else: | ||
| # Auto-select if only one cluster | ||
| clusters = self.detectDb2uClusters(namespace) | ||
| if len(clusters) == 0: | ||
| self.fatalError(f"No Db2uClusters found in namespace {namespace}") | ||
| elif len(clusters) == 1: | ||
| clusterName = clusters[0].metadata.name | ||
| else: | ||
| self.fatalError("Multiple clusters found. Please specify --cluster-name") | ||
|
|
||
| # Confirmation | ||
| if not self.noConfirm: | ||
| self.printH2("Migration Summary") | ||
| print_formatted_text( | ||
| HTML( | ||
| f"<Yellow>Namespace:</Yellow> {namespace}\n" | ||
| f"<Yellow>Cluster:</Yellow> {clusterName}\n" | ||
| f"<Yellow>Backup:</Yellow> {'Enabled' if enableBackup else 'Disabled'}\n" | ||
| ) | ||
| ) | ||
| if not self.yesOrNo("Proceed with migration"): | ||
| print_formatted_text(HTML("<Red>Migration cancelled</Red>")) | ||
| return | ||
|
|
||
| # Set parameters | ||
| self.setParam("db2_migration_namespace", namespace) | ||
| self.setParam("db2_migration_cluster_name", clusterName) | ||
| self.setParam("db2_migration_backup_enabled", str(enableBackup).lower()) | ||
|
|
||
| # Prepare pipeline namespace | ||
| pipelinesNamespace = "mas-pipelines" | ||
|
|
||
| with Halo(text="Validating OpenShift Pipelines installation", spinner=self.spinner) as h: | ||
| if installOpenShiftPipelines(self.dynamicClient): | ||
| h.succeed("OpenShift Pipelines Operator is installed and ready") | ||
| else: | ||
| h.fail("OpenShift Pipelines Operator installation failed") | ||
| self.fatalError("Installation failed") | ||
|
|
||
| with Halo(text=f"Preparing namespace ({pipelinesNamespace})", spinner=self.spinner) as h: | ||
| createNamespace(self.dynamicClient, pipelinesNamespace) | ||
| preparePipelinesNamespace(dynClient=self.dynamicClient) | ||
| h.succeed(f"Namespace {pipelinesNamespace} is ready") | ||
|
|
||
| with Halo(text=f"Installing latest Tekton definitions (v{self.version})", spinner=self.spinner) as h: | ||
| updateTektonDefinitions(self.dynamicClient, pipelinesNamespace, self.tektonDefsPath) | ||
| h.succeed(f"Latest Tekton definitions are installed (v{self.version})") | ||
|
|
||
| # Launch pipeline | ||
| with Halo(text="Submitting PipelineRun for DB2 migration", spinner=self.spinner) as h: | ||
| pipelineURL = launchDb2MigrationPipeline(dynClient=self.dynamicClient, params=self.params) | ||
| if pipelineURL: | ||
| h.succeed("PipelineRun for DB2 migration submitted") | ||
| print_formatted_text(HTML(f"\nView progress:\n <Cyan><u>{pipelineURL}</u></Cyan>\n")) | ||
| else: | ||
| h.fail("Failed to submit PipelineRun, see log file for details") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| # ----------------------------------------------------------- | ||
| # Licensed Materials - Property of IBM | ||
| # 5737-M66 | ||
| # (C) Copyright IBM Corp. 2026 All Rights Reserved. | ||
| # US Government Users Restricted Rights - Use, duplication, or disclosure | ||
| # restricted by GSA ADP Schedule Contract with IBM Corp. | ||
| # ----------------------------------------------------------- | ||
|
|
||
| import argparse | ||
| import sys | ||
|
|
||
| from ..cli import getHelpFormatter | ||
|
|
||
|
|
||
| class Db2MigrationArgumentParser(argparse.ArgumentParser): | ||
| """Custom argument parser for DB2 migration command""" | ||
|
|
||
| def format_usage(self): | ||
| prog = self.prog | ||
| return ( | ||
| f"Usage (non-interactive mode):\n" | ||
| f" {prog} --namespace NAMESPACE [--cluster-name CLUSTER_NAME]\n" | ||
| f" [--backup {{true,false}}] [--no-confirm]\n" | ||
| f"\n" | ||
| f"Usage (interactive mode):\n" | ||
| f" {prog}\n" | ||
| f"\n" | ||
| f"Usage (help):\n" | ||
| f" {prog} -h\n" | ||
| ) | ||
|
|
||
| def parse_args(self, args=None, namespace=None): | ||
| parsedArgs = super().parse_args(args, namespace) | ||
|
|
||
| providedArgs = [] | ||
| if args is not None: | ||
| providedArgs = [arg for arg in args if arg.startswith("-")] | ||
| else: | ||
| providedArgs = [arg for arg in sys.argv[1:] if arg.startswith("-")] | ||
|
|
||
| hasAnyArgs = len(providedArgs) > 0 | ||
| hasNamespace = parsedArgs.namespace is not None | ||
| helpOnly = "--help" in providedArgs or "-h" in providedArgs | ||
|
|
||
| if hasAnyArgs and not hasNamespace and not helpOnly: | ||
| self.error("non-interactive mode requires --namespace parameter") | ||
|
|
||
| return parsedArgs | ||
|
|
||
|
|
||
| db2MigrationArgParser = Db2MigrationArgumentParser( | ||
| prog="mas db2ucluster-migration", | ||
| description="Migrate Db2uCluster to Db2uInstance by launching the DB2 Migration Tekton Pipeline.", | ||
| epilog="Refer to the online documentation for more information: https://ibm-mas.github.io/cli/", | ||
| formatter_class=getHelpFormatter(), | ||
| add_help=False, | ||
| ) | ||
|
|
||
| migrationArgGroup = db2MigrationArgParser.add_argument_group("Migration Configuration", "Configure the DB2 migration parameters.") | ||
| migrationArgGroup.add_argument("--namespace", required=False, help="Namespace containing Db2uCluster instances") | ||
| migrationArgGroup.add_argument("--cluster-name", required=False, help="Specific Db2uCluster name to migrate") | ||
| migrationArgGroup.add_argument("--backup", required=False, choices=["true", "false"], help="Enable or disable backup before migration") | ||
|
|
||
| otherArgGroup = db2MigrationArgParser.add_argument_group("More", "Additional options.") | ||
| otherArgGroup.add_argument("--no-confirm", required=False, action="store_true", default=False, help="Launch without prompting for confirmation") | ||
| otherArgGroup.add_argument("-h", "--help", action="help", default=False, help="Show this help message and exit") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename clusters to
db2_clustersfor clarity.