def __init__(self, ctx, hname, user): self._userFull = f'{user.name}@{hname}' self._authKeys = None # Prepare command execution self._cmdShell = CmdShell() self._cmdSsh = CmdSsh(ctx, hname, user) self._rsyncSsh, self._rsyncSshSecrets = self._cmdSsh.getSshCmdAndSecrets( withLogin=False) # Get the path to the remote authorized_keys file homeDir = getHomeDir(ctx, hname, user) if not homeDir: fail( f"Could not determine the home directory of '{self._userFull}'" ) self._akPath = f'{homeDir}/.ssh/authorized_keys' # Read the authorized_keys file info = _PublicKeyInformation(hname, user, self._akPath) self._read(info) # Sets self._authKeys logging.debug( f'self._authKeys >>>\n{self._authKeys}\n<<< self._authKeys')
def getValidNfsServerAddress(ctx): """ Get valid NFS server address serving the OCP cluster net """ # Get all IP addresses for NFS server allIps = getAllNfsServerIpAddresses(ctx) # Need an OC login to get worker node address ocp = Ocp(ctx, login="******", verify=True) worker = ocp.getWorkerNodeList()[0] # Run Python script tools/modules/nfs-ping-test on helper node (in-line) cmdShell = CmdShell() host = ctx.cf.ocp.helper.host.name user = ctx.cr.ocp.helper.user repo = ctx.cf.build.repo.root toolCmd = f'python3 - <{repo}/tools/modules/nfs-ping-test {worker} {allIps}' cmdSsh = CmdSsh(ctx, host, user, reuseCon=False) runCmd, secr = cmdSsh.getSshCmdAndSecrets(withLogin=True) ipAddr = cmdShell.run(f'{runCmd} {toolCmd}', secrets=secr).out logging.debug( f"Running shell cmd: '{runCmd} {toolCmd}' returns '{ipAddr}'") if ipAddr == 'None': message = "Could not identify valid IP address for the NFS server" message += f"on worker node {worker}.\n" fail(message) del ocp return ipAddr
def __init__(self, ctx): self._ctx = ctx self._host = None self._user = None self._cmdShell = CmdShell() self._cmdSsh = None self._remoteCopy = None self._flavor = None self._description = None
def podmanOcpRegistryLogin(self): """ Log into the default registry of an OpenShift cluster """ out = CmdShell().run( 'podman login' ' --tls-verify=false' ' -u $(oc whoami) -p $(oc whoami --show-token)' f' default-route-openshift-image-registry.apps.{self._domain}').out if not out.startswith('Login Succeeded!'): fail('podman login failed')
def getPublicKey(ctx, hname, user): """ Get the public key from a public key file of a specifc user at a specific host """ userFull = f'{user.name}@{hname}' # Prepare command execution and determine path of public key file if hname == getBuildHost().name: cmd = CmdShell() defaultPubKeyPath = f'{Path.home()}/.ssh/id_rsa.pub' else: cmd = CmdSsh(ctx, hname, user) defaultPubKeyPath = f'{getHomeDir(ctx, hname, user)}/.ssh/id_rsa.pub' if f'{user.sshid}': pubKeyPath = f'{user.sshid}.pub' else: pubKeyPath = defaultPubKeyPath # Read public key file res = cmd.run(f'cat {pubKeyPath}') if res.rc != 0: fail( f"Could not get public key record of user '{userFull}' from file '{pubKeyPath}'" ) # Get the public key from the public key file content info = _PublicKeyInformation(hname, user, pubKeyPath) pubKeys = _PublicKeys(res.out, info, keepAll=False) logging.debug(f'pubKeys >>>\n{pubKeys}\n<<< pubKeys') if pubKeys.numKeys() == 0: fail(f'Public key file of {userFull} does not contain any key record') if pubKeys.numKeys() > 1: records = '\n\n'.join(pubKeys) fail(f"Public key file '{pubKeyPath}' of {userFull}" f" contains multiple records:\n\n{records}") pubKey = pubKeys.getKey(0) logging.debug(f'Public key for {userFull}: >>>\n{pubKey}\n<<<') return pubKey
def getAppNames(self): """ Get the deployment app names """ res = CmdShell().run( 'oc get pods -o template --template "{{range .items}}{{.metadata.labels.app}} {{end}}"' ) if res.rc > 0: return [] return res.out.split()
def getProject(self): """ get the project name from OpenShift """ res = CmdShell().run( f"oc get project {self._project}" " -o custom-columns=NAME:.metadata.name --no-headers") if res.rc > 0: return "" return res.out
def getSecret(self): """ get the secret from OpenShift """ res = CmdShell().run( f"oc get secret --namespace {self._project}" f" --field-selector 'metadata.name={self._ocp.containers.di.secret}'" " -o custom-columns=NAME:.metadata.name --no-headers") if res.rc > 0: return "" return res.out
def getWorkerNodeList(self): """ get the list of worker nodes from OpenShift """ res = CmdShell().run('oc get nodes' ' --selector="node-role.kubernetes.io/worker"' " -o template --template" " '{{range .items}}{{.metadata.name}} {{end}}'") if res.rc > 0: return [] return res.out.split()
def getNodePortList(self): """ Get the node ports on the worker node which can be used to connect to the SAP system """ res = CmdShell().run( f'oc get service {self._appName}-np' ' -o template --template "{{range .spec.ports}}{{.name}}:{{.nodePort}} {{end}}"' ) if res.rc > 0: return [] return res.out.split()
def _runCmd(cmd): result = CmdShell().run(cmd) if result.rc != 0: msg = '' msg += f"Command '{cmd}' failed" msg += f'\n stdout: >>>{result.out}<<<' msg += f'\n stderr: >>>{result.err}<<<' msg += f'\n rc: {result.rc}' fail(msg) return result
def ocLogin(self, user=""): """ Log into an OpenShift cluster with given user """ if not user: return self._result secrets = [user.password] return CmdShell().run( 'oc login' ' --insecure-skip-tls-verify=true' f' https://api.{self._domain}:6443' f' -u {user.name}' ' -p :0:', secrets)
def ocServiceAccountExists(self): """ Return True if Service Account exists for ocp-project """ res = CmdShell().run( "oc get sa" f" --namespace {self._project}" f" --field-selector 'metadata.name={self._ocp.sa.name}'" " -o custom-columns=NAME:.metadata.name" " --no-headers") if not self._ocp.sa.name in res.out: return False return True
def isRepoAccessible(repository): """ Returns True if repository is accessible, otherwise False """ if repository == "": return True cmd = f'dnf repolist --enabled | grep {repository}' out = CmdShell().run(cmd).out if out != "": logging.debug(f'Access to repository {repository} is enabled') return True logging.debug(f'Cannot access repository {repository}') return False
def _runRsync(self, source, filterFilePath, verbose, dryRun): logging.debug(f'source: >>>{source}<<<') cmdShell = CmdShell() cmd = 'rsync -a --relative' cmd += f' -e "{self._rsyncSsh}"' if verbose > 0: cmd += ' -'+'v'*verbose cmd += f' -f "merge {filterFilePath}"' if dryRun: cmd += ' -n' if not isinstance(source, list): cmd += f' {self._user.name}@{self._host}:{source} ./' cmdShell.run(cmd, self._rsyncSshSecrets) else: with tempfile.NamedTemporaryFile(mode='w') as tfh: tfh.write("\n".join(str(fn) for fn in source)) tfh.flush() logging.debug(f"Contents of file '{tfh.name}':") logging.debug('>>>') # pylint: disable=unspecified-encoding with open(tfh.name) as rfh: logging.debug(rfh.read()) logging.debug('<<<') cmd += f' -r --files-from={tfh.name}' cmd += f' {self._user.name}@{self._host}:/ ./' cmdShell.run(cmd, self._rsyncSshSecrets)
def ocApply(self, file, printRunTime=False): """ Apply a configuration to a resource """ cmd = f"oc apply -f {file}" if printRunTime: cmd = "time " + cmd res = CmdShell().run(cmd) if res.rc == 0: logging.debug(f"Configuration file {file} successfully applied") else: logging.debug(f"Error applying configuration file {file}") return res
def ocDelete(self, file, printRunTime=False): """ Delete a configuration """ cmd = f"oc delete -f {file}" if printRunTime: cmd = "time " + cmd res = CmdShell().run(cmd) if res.rc == 0: logging.debug(f"Configuration file {file} successfully removed") else: logging.debug(f"Error removing configuration file {file}") return res
def containerRun(self, containerName, command, rcOk=(0, )): """ Run a command in a running container of given flavor """ logging.debug(f'rcOk >>>{rcOk}<<<') podName = self.getPodName() if not podName: res = Command.buildResult('', 'Cannot get pod name', 1, rcOk=(1, )) else: ocCmd = self._buildOcExecCmd(podName, containerName, command) res = CmdShell().run(ocCmd, rcOk=rcOk) return res
def getServiceAccountListForScc(self, scc): """ get the list of service accounts for scc from OpenShift """ tplList = { "anyuid": str('{{range .groups}}{{.}} {{end}}'), "hostmount-anyuid": str('{{range .users}}{{.}} {{end}}') } template = tplList[scc] res = CmdShell().run(f"oc adm policy who-can use scc {scc} -o template" f" --template='{template}'" f" --namespace={self._project}") if res.rc > 0: return [] return res.out.split()
def getHdbConnectSecretUser(self): """ Get credentials currently stored in OCP secret of name ctx.cf.ocp.containers.di.secret Returns an object 'user' where - 'user.name' holds the retrieved user name - 'user.password' holds the retrieved password in case of success Returns None in case of failure (also if ctx.cf.ocp.containers.di.secret is not defined) """ secretName = self._ocp.containers.di.secret template = str( '{{(index (index .items 0).metadata.annotations' ' "kubectl.kubernetes.io/last-applied-configuration")}}') res = CmdShell().run(f'oc get secret' f" --namespace '{self._project}'" f" --field-selector 'metadata.name={secretName}'" f" -o template --template '{template}'") if res.rc == 0: try: secretData = yaml.load(res.out, Loader=yaml.Loader)['stringData'] user = types.SimpleNamespace() user.name = secretData['HDB_DBUSER'] user.password = secretData['HDB_DBUSERPWD'] except KeyError as kex: user = None logging.debug( f"Could not evaluate secret data of OCP secret '{secretName}' ({kex})" ) else: user = None logging.debug( f"Could not retrieve secret data from OCP secret '{secretName}'" ) return user
def __init__(self, ctx, create=False): self._noFileMsg = f"Credentials file '{ctx.ar.creds_file}' does not exist" # Determine encryption status of ctx.ar.creds_file # The file is considered not to be encrypted # - if a new credentials file is created and CLI argument # '--unencypted' is set # - otherwise, if the file exists, encryption status of the file is # derived from the file type returned by the Linux 'file' command, # taking into consideration that the file maybe a symlink. credsfile = os.path.realpath(ctx.ar.creds_file) if create: # This case can only occur if the calling tool is 'tools/creds -n' self._unencrypted = ctx.ar.unencrypted elif pathlib.Path(credsfile).is_file(): out = CmdShell().run(f'file -b {credsfile} --mime-type').out self._unencrypted = 'application/pgp' not in out else: # This case will lead to abort of the calling tool since # super().__init__() will finally try to read the non-existing file. # Nevertheless we need to set attribute self._unencrypted since it # is accessed before the calling tool is aborted. # Set it to True since this avoids some unnecessary actions. self._unencrypted = True self._gpg = self._getGpg() self._recipient = ctx.ar.recipient if hasattr(ctx.ar, 'recipient') else None self._passphrase = os.getenv('SOOS_CREDS_PASSPHRASE') super().__init__(ctx, './creds.yaml.template', ctx.ar.creds_file, create) if create: return logging.debug(f"Assuming file '{ctx.ar.creds_file}'" f" is{' not' if self._unencrypted else ''} encrypted")
def getRpmFileForPackage(packageName, path): """ Return filename for specified package """ # do not use fail function here, cause verify-config uses this function too if os.path.exists(path): pathContent = os.listdir(path) if len(pathContent) < 1: raise RpmFileNotFoundException(path, packageName, 'is empty.') cmd = 'rpm -qp --queryformat "%{NAME}" ' for file in pathContent: if os.path.isfile(path + '/' + file): # check if rpm is found packageNameFromRpm = CmdShell().run(cmd + path + '/' + file).out if packageNameFromRpm == packageName: return file else: raise RpmFileNotFoundException(path, packageName, 'does not exist.') raise RpmFileNotFoundException(path, packageName, 'does not contain a matching rpm package file.')
def _getPodProperty(self, propertyName, propertySelector): """ Get a property of the pod in which our current deployment is running """ podProperty = '' if not self._appName: fail("Internal error: appName not set") res = CmdShell().run(f'oc get pods --selector="app={self._appName}"' ' -o template --template "{{range .items}}{{' + propertySelector + '}}{{end}}"') if res.rc == 0: podProperty = res.out.strip() logging.debug(f"Pod property '{propertyName}': '{podProperty}'") else: logging.debug(f"Could not get pod property '{propertyName}'" f" for app '{self._appName}' (reason: {res.err})") return podProperty
class Builder(): """ Build container images """ # pylint: disable=too-many-instance-attributes def __init__(self, ctx): self._ctx = ctx self._host = None self._user = None self._cmdShell = CmdShell() self._cmdSsh = None self._remoteCopy = None self._flavor = None self._description = None def buildImage(self, sidU, host, user): """ Build image """ # pylint: disable=too-many-locals,too-many-statements repoRoot = self._ctx.cf.build.repo.root buildTmpRoot = self._ctx.ar.temp_root buildDir = self._ctx.ar.build_directory keepFiles = self._ctx.ar.keep_files # Initialize ssh connection if not self._cmdSsh or host != self._host or user != self._user: # Initialize only if not yet initialized or if connection parameters have changed if self._cmdSsh: del self._cmdSsh self._cmdSsh = CmdSsh(self._ctx, host, user) # Initialize remote copy connection if not self._remoteCopy or host != self._host or user != self._user: # Initialize only if not yet initialized or if connection parameters have changed if self._remoteCopy: del self._remoteCopy self._remoteCopy = RemoteCopy(self._ctx, host, user) self._host = host self._user = user # System ID sidL = sidU.lower() logging.debug(f"sidU: '{sidU}'") logging.debug(f"sidL: '{sidL}'") # Directories dirs = types.SimpleNamespace() dirs.repoRoot = repoRoot if buildDir and len(buildDir) != 0: dirs.build = buildDir else: self._cmdShell.run(f'mkdir -p "{buildTmpRoot}"') dirs.build = self._cmdShell.run( f'mktemp -d -p "{buildTmpRoot}" ' f'-t soos-build-{self._flavor}.XXXXXXXXXX').out dirs.usrSapReal = self._getUsrSapReal() dirs.sapmnt = self._ctx.cf.refsys.nws4.base.sapmnt self._setDirsFlavor(sidU, dirs) logging.debug(f"dirs: '{dirs}'") # Image properties image = types.SimpleNamespace() image.name = f'localhost/soos-{sidL}' image.version = 'latest' image.tag = f'{image.name}:{image.version}' image.date = date.today().strftime('%Y-%m-%d') image.description = self._description # Must be set by derived class with pushd(dirs.repoRoot): image.commit = self._cmdShell.run('git log --pretty="%H" -1').out image.branch = self._cmdShell.run( 'git rev-parse --abbrev-ref HEAD').out logging.debug(f"image: '{image}'") # OS user properties (sapadm, sidadm, sapsysGid) = self._getOsUserProperties(sidL) logging.debug(f"sapadm : '{sapadm}'") logging.debug(f"sidadm : '{sidadm}'") logging.debug(f"sapsysGid: '{sapsysGid}'") # Misc buildCmd = 'podman' remoteOs = 'linux' + self._cmdSsh.run('uname -m').out # Start build process with tempfile.TemporaryDirectory() as dirs.tmp: logging.debug(f"Created temporary directory '{dirs.tmp}'") self._cleanupAtStart(dirs, keepFiles) self._genBuildContext(sidU, dirs, sapadm, sidadm, sapsysGid, host, remoteOs) containerfile = self._genContainerfile(sidU, dirs, image, sapadm, sidadm, sapsysGid) self._buildImage(buildCmd, dirs, image, containerfile) self._cleanupAtEnd(dirs) def _getUsrSapReal(self): # Check whether /usr/sap is a real directory or a symlink to another directory usrSapReal = self._cmdSsh.run('readlink /usr/sap').out if len(usrSapReal) != 0: logging.info( f"Detected that '/usr/sap' is a symbolic link to '{usrSapReal}'" ) else: usrSapReal = '/usr/sap' logging.debug(f"usrSapReal: '{usrSapReal}'") return usrSapReal def _setDirsFlavor(self, sidU, dirs): # Flavor specific directories # pylint: disable=unused-argument fail( 'This function must be overwritten by derived flavor specific builder class.' ) def _getOsUserProperties(self, sidL): # Get properties of sapadm and <sid>adm from remote host /etc/passwd sapadm = types.SimpleNamespace() (_d1, _d2, sapadm.uid, sapsysGid, sapadm.comment, sapadm.home, sapadm.shell ) = self._cmdSsh.run('grep "^sapadm:" /etc/passwd').out.split(':') sidadm = types.SimpleNamespace() (_d1, _d2, sidadm.uid, _d4, sidadm.comment, sidadm.home, sidadm.shell ) = self._cmdSsh.run(f'grep "^{sidL}adm:" /etc/passwd').out.split(':') logging.debug(f'Returning {sapadm}, {sidadm}, {sapsysGid}') return (sapadm, sidadm, sapsysGid) def _cleanupAtStart(self, dirs, keepFiles): # Remove previously copied files if not explicitly asked to keep them if not keepFiles: logging.info( f"##### Cleaning up build directoy '{dirs.build}' #####") with pushd(dirs.build): self._cmdShell.run('rm -rf ..?* .[!.]* *') def _genBuildContext(self, sidU, dirs, sapadm, sidadm, sapsysGid, host, remoteOs): # Generate podman build context # pylint: disable=too-many-arguments filterFilePath = f'{dirs.tmp}/rsync-filter' logging.debug(f"filterFilePath: {filterFilePath}") try: # pylint: disable=invalid-name, unspecified-encoding with open(filterFilePath, 'w') as fh: print(self._getRsyncFilter(sidU, dirs, remoteOs), file=fh) except IOError: fail(f"Error writing to file {filterFilePath}") self._genBuildContextFlavor(sidU, dirs, sapadm, sidadm, sapsysGid, host, filterFilePath) def _getRsyncFilter(self, sidU, dirs, remoteOs): # Get filter for selective copy depending on flavor # pylint: disable=unused-argument fail( 'This function must be overwritten by derived flavor specific builder class.' ) def _genBuildContextFlavor(self, sidU, dirs, sapadm, sidadm, sapsysGid, host, filterFilePath): # Flavor dependent actions for build context generation # pylint: disable=unused-argument,too-many-arguments fail( 'This function must be overwritten by derived flavor specific builder class.' ) def _genContainerfile(self, sidU, dirs, image, sapadm, sidadm, sapsysGid): # Generate containerfile from template depending on flavor # MUST RUN AFTER BUILD CONTEXT SETUP # pylint: disable=too-many-arguments logging.info("##### Generating Containerfile #####") sidL = sidU.lower() # Common parameters if dirs.usrSapReal != '/usr/sap': usrSapLinkCmd = f'ln -s {dirs.usrSapReal} /usr/sap' else: usrSapLinkCmd = 'true' # get optional packages packages = getattr(self._ctx.cf.images, self._flavor).packages pkgParams = self._getOptionalPackageParams(packages, dirs) params = { 'IMAGE_BRANCH': image.branch, 'IMAGE_COMMIT': image.commit, 'IMAGE_DATE': image.date, 'IMAGE_DESCRIPTION': image.description, 'IMAGE_VERSION': image.version, 'SAPADM_COMMENT': sapadm.comment, 'SAPADM_HOME': sapadm.home, 'SAPADM_SHELL': sapadm.shell, 'SAPADM_UID': sapadm.uid, 'SAPMNT': dirs.sapmnt, 'SAPSYS_GID': sapsysGid, 'sid': sidL, 'SID': sidU, 'SIDADM_COMMENT': sidadm.comment, 'SIDADM_HOME': sidadm.home, 'SIDADM_SHELL': sidadm.shell, 'SIDADM_UID': sidadm.uid, 'USR_SAP_REAL': dirs.usrSapReal, 'USR_SAP_LINK_CMD': usrSapLinkCmd, 'INSTALL_OPT_PACKAGES': pkgParams.installOptPackagesDnf, 'COPY_OPT_PACKAGE_FILES': pkgParams.copyOptPackageFiles, 'INSTALL_OPT_PACKAGE_FILES': pkgParams.installOptPackageFiles } params.update(self._getContainerfileParams(sidU, dirs)) containerfile = f'{dirs.tmp}/containerfile' template = f'{dirs.repoRoot}/openshift/images/{self._flavor}/containerfile.template' genFileFromTemplate(template, containerfile, params) try: # pylint: disable=invalid-name, unspecified-encoding with open(containerfile) as fh: logging.debug( f"Contents of '{containerfile}': >>>\n{fh.read()}<<<") except IOError: fail(f"Error reading from {containerfile}") return containerfile def _getContainerfileParams(self, sidU, dirs): # Non-common containerfile template parameters depending on flavor # pylint: disable=unused-argument fail( 'This function must be overwritten by derived flavor specific builder class.' ) def _buildImage(self, buildCmd, dirs, image, containerfile): # Build image # MUST RUN AFTER BUILD CONTEXT SETUP # pylint: disable=no-self-use logging.info("##### Building image #####") with pushd(dirs.build): self._cmdShell.run( f'{buildCmd} build -t {image.tag} -f "{containerfile}" .') def _getOptionalPackageParams(self, packages, dirs): # Check if optional packages must be installed # and set them pkgParams = types.SimpleNamespace() pkgParams.installOptPackagesDnf = '' pkgParams.copyOptPackageFiles = '' pkgParams.installOptPackageFiles = '' if len(packages) > 0: self._addDependencies(packages, pkgParams) self._addDnfInstallablePackages(packages, pkgParams) self._addRpmPackages(packages, pkgParams, dirs) return pkgParams def _addDependencies(self, packages, pkgParams): # Set dependencies for optional packages firstRun = pkgParams.installOptPackagesDnf == "" for package in packages: if len(package.dependencies) > 0: if firstRun: pkgParams.installOptPackagesDnf = 'RUN dnf -y install' firstRun = False else: pkgParams.installOptPackagesDnf += ' && \\' + '\n' pkgParams.installOptPackagesDnf += ' dnf -y install' for dependency in package.dependencies: logging.debug(f"Adding dependency '{dependency}' " + f"for package '{package.packageName}'") pkgParams.installOptPackagesDnf += f' {dependency}' def _addDnfInstallablePackages(self, packages, pkgParams): # set all packages to be installed using dnf firstRun = pkgParams.installOptPackagesDnf == "" for package in packages: if package.dnfInstallable: logging.debug( f'package {package.packageName} installable via dnf install' ) if firstRun: pkgParams.installOptPackagesDnf = 'RUN dnf -y install' firstRun = False else: pkgParams.installOptPackagesDnf += ' && \\' + '\n' pkgParams.installOptPackagesDnf += ' dnf -y install' if package.repository != "": pkgParams.installOptPackagesDnf += f' --enablerepo={package.repository}' logging.debug( f'enabling repository : {package.repository}') pkgParams.installOptPackagesDnf += f' {package.packageName}' def _addRpmPackages(self, packages, pkgParams, dirs): # set all packages which must be copied and installed using rpm firstRun = pkgParams.copyOptPackageFiles == "" for package in packages: if not package.dnfInstallable: logging.debug( f'package {package.packageName} must be installed via rpm') if firstRun: pkgParams.copyOptPackageFiles = 'COPY ' pkgParams.installOptPackageFiles = 'RUN ' firstRun = False else: pkgParams.copyOptPackageFiles += ' && \\' + '\n' + ' ' pkgParams.installOptPackageFiles += ' && \\' + '\n' + ' ' try: rpmFileName = getRpmFileForPackage(package.packageName, dirs.defaultPackagesDir) pkgParams.copyOptPackageFiles += f'{dirs.defaultPackagesDir}' pkgParams.copyOptPackageFiles += f'/{rpmFileName} / ' pkgParams.installOptPackageFiles += f'rpm -i /{rpmFileName} && \\' + '\n' pkgParams.installOptPackageFiles += f' rm /{rpmFileName}' except RpmFileNotFoundException as exp: fail(exp.errorText) def _cleanupAtEnd(self, dirs): # Cleanup after image build with pushd(dirs.repoRoot): # self._cmdShell.run(f'\\rm -rf {dirs.build}') pass
def setProject(self): """ Set project """ return CmdShell().run(f"oc project {self._project}")
def serviceDelete(self, serviceName): """ deletes the specified service """ if self.serviceExists(serviceName): res = CmdShell().run(f"oc delete service {serviceName}") print(res)
def serviceExists(self, serviceName): """ Returns True if the specified NodePort Service exists """ res = CmdShell().run(f"oc get service {serviceName}") return res.rc == 0
def isProjectExisting(self): """ Does the specified oc project exist """ res = CmdShell().run(f"oc get project {self._project}") return res.rc == 0
def createProject(self): """ Create project """ print(f"creating project: {self._project}") return CmdShell().run(f"oc new-project {self._project}")
def ocLogout(self): """ Logout from OpenShift Cluster """ return CmdShell().run('oc logout').rc