1
0
mirror of https://github.com/openSUSE/osc.git synced 2024-12-25 17:36:13 +01:00

Merge pull request #1193 from dmach/commandline-project-package

commandline: Migrate project, package argument parsing to pop_project_package_from_args()
This commit is contained in:
Daniel Mach 2022-12-13 09:02:49 +01:00 committed by GitHub
commit 85d86249d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1245 additions and 218 deletions

View File

@ -0,0 +1,32 @@
Feature: `osc addchannels` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "checkout test:factory test-pkgA"
And I set working directory to "{context.osc.temp}/test:factory/test-pkgA"
Scenario: Run `osc addchannels`
When I execute osc with args "addchannels"
Then stdout is
"""
Adding channels to package 'test:factory/test-pkgA'
"""
Scenario: Run `osc addchannels --enable-all`
When I execute osc with args "addchannels --enable-all"
Then stdout is
"""
Adding channels to package 'test:factory/test-pkgA' options: enable-all
"""
Scenario: Run `osc addchannels --skip-disabled`
When I execute osc with args "addchannels --skip-disabled"
Then stdout is
"""
Adding channels to package 'test:factory/test-pkgA' options: skip-disabled
"""

View File

@ -0,0 +1,32 @@
Feature: `osc addchannels` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "checkout test:factory"
And I set working directory to "{context.osc.temp}/test:factory"
Scenario: Run `osc addchannels`
When I execute osc with args "addchannels"
Then stdout is
"""
Adding channels to project 'test:factory'
"""
Scenario: Run `osc addchannels --enable-all`
When I execute osc with args "addchannels --enable-all"
Then stdout is
"""
Adding channels to project 'test:factory' options: enable-all
"""
Scenario: Run `osc addchannels --skip-disabled`
When I execute osc with args "addchannels --skip-disabled"
Then stdout is
"""
Adding channels to project 'test:factory' options: skip-disabled
"""

View File

@ -0,0 +1,38 @@
Feature: `osc addchannels` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
Scenario: Run `osc addchannels <project> <package>`
When I execute osc with args "addchannels test:factory test-pkgA"
Then stdout is
"""
Adding channels to package 'test:factory/test-pkgA'
"""
Scenario: Run `osc addchannels <project>/<package>`
When I execute osc with args "addchannels test:factory/test-pkgA"
Then stdout is
"""
Adding channels to package 'test:factory/test-pkgA'
"""
Scenario: Run `osc addchannels <project> <package> --enable-all`
When I execute osc with args "addchannels test:factory test-pkgA --enable-all"
Then stdout is
"""
Adding channels to package 'test:factory/test-pkgA' options: enable-all
"""
Scenario: Run `osc addchannels <project> <package> --skip-disabled`
When I execute osc with args "addchannels test:factory test-pkgA --skip-disabled"
Then stdout is
"""
Adding channels to package 'test:factory/test-pkgA' options: skip-disabled
"""

View File

@ -0,0 +1,31 @@
Feature: `osc addchannels` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
Scenario: Run `osc addchannels <project>`
When I execute osc with args "addchannels test:factory"
Then stdout is
"""
Adding channels to project 'test:factory'
"""
Scenario: Run `osc addchannels <project> --enable-all`
When I execute osc with args "addchannels test:factory --enable-all"
Then stdout is
"""
Adding channels to project 'test:factory' options: enable-all
"""
Scenario: Run `osc addchannels <project> --skip-disabled`
When I execute osc with args "addchannels test:factory --skip-disabled"
Then stdout is
"""
Adding channels to project 'test:factory' options: skip-disabled
"""

View File

@ -0,0 +1,24 @@
Feature: `osc addcontainers` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "checkout test:factory test-pkgA"
And I set working directory to "{context.osc.temp}/test:factory/test-pkgA"
Scenario: Run `osc addcontainers`
When I execute osc with args "addcontainers"
Then stdout is
"""
Adding containers to package 'test:factory/test-pkgA'
"""
Scenario: Run `osc addcontainers --extend-package-names`
When I execute osc with args "addcontainers --extend-package-names"
Then stdout is
"""
Adding containers to package 'test:factory/test-pkgA' options: extend-package-names
"""

View File

@ -0,0 +1,17 @@
Feature: `osc addcontainers` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "checkout test:factory"
And I set working directory to "{context.osc.temp}/test:factory"
Scenario: Run `osc addcontainers`
When I execute osc with args "addcontainers"
Then the exit code is 1
And stderr is
"""
Directory '{context.osc.temp}/test:factory' is not a working copy of a package
"""

View File

@ -0,0 +1,30 @@
Feature: `osc addcontainers` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
Scenario: Run `osc addcontainers <project> <package>`
When I execute osc with args "addcontainers test:factory test-pkgA"
Then stdout is
"""
Adding containers to package 'test:factory/test-pkgA'
"""
Scenario: Run `osc addcontainers <project>/<package>`
When I execute osc with args "addcontainers test:factory/test-pkgA"
Then stdout is
"""
Adding containers to package 'test:factory/test-pkgA'
"""
Scenario: Run `osc addcontainers <project> <package> --extend-package-names`
When I execute osc with args "addcontainers test:factory test-pkgA --extend-package-names"
Then stdout is
"""
Adding containers to package 'test:factory/test-pkgA' options: extend-package-names
"""

View File

@ -0,0 +1,16 @@
Feature: `osc develproject` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "checkout test:factory test-pkgA"
And I set working directory to "{context.osc.temp}/test:factory/test-pkgA"
Scenario: Run `osc develproject`
When I execute osc with args "develproject"
Then stdout is
"""
test:devel/test-pkgA
"""

View File

@ -0,0 +1,17 @@
Feature: `osc develproject` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "checkout test:factory"
And I set working directory to "{context.osc.temp}/test:factory"
Scenario: Run `osc develproject`
When I execute osc with args "develproject"
Then the exit code is 1
And stderr is
"""
Directory '{context.osc.temp}/test:factory' is not a working copy of a package
"""

View File

@ -0,0 +1,32 @@
Feature: `osc develproject` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
Scenario: Run `osc develproject`
When I execute osc with args "develproject test:factory test-pkgA"
Then stdout is
"""
test:devel/test-pkgA
"""
Scenario: Run `osc develproject`
When I execute osc with args "develproject test:factory test-pkgB"
Then the exit code is 1
And stderr is
"""
Package test:factory/test-pkgB has no devel project
"""
Scenario: Run `osc develproject`
When I execute osc with args "develproject test:factory/test-pkgB"
Then the exit code is 1
And stderr is
"""
Package test:factory/test-pkgB has no devel project
"""

View File

@ -0,0 +1,15 @@
Feature: `osc develproject` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
Scenario: Run `osc develproject`
When I execute osc with args "develproject test:factory"
Then the exit code is 1
And stderr is
"""
*** Error: Please specify a package
"""

View File

@ -0,0 +1,16 @@
Feature: `osc enablechannels` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "checkout test:factory test-pkgA"
And I set working directory to "{context.osc.temp}/test:factory/test-pkgA"
Scenario: Run `osc enablechannels`
When I execute osc with args "enablechannels"
Then stdout is
"""
Enabling channels in package 'test:factory/test-pkgA'
"""

View File

@ -0,0 +1,16 @@
Feature: `osc enablechannels` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "checkout test:factory"
And I set working directory to "{context.osc.temp}/test:factory"
Scenario: Run `osc enablechannels`
When I execute osc with args "enablechannels"
Then stdout is
"""
Enabling channels in project 'test:factory'
"""

View File

@ -0,0 +1,22 @@
Feature: `osc enablechannels` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
Scenario: Run `osc enablechannels <project> <package>`
When I execute osc with args "enablechannels test:factory test-pkgA"
Then stdout is
"""
Enabling channels in package 'test:factory/test-pkgA'
"""
Scenario: Run `osc enablechannels <project>/<package>`
When I execute osc with args "enablechannels test:factory/test-pkgA"
Then stdout is
"""
Enabling channels in package 'test:factory/test-pkgA'
"""

View File

@ -0,0 +1,14 @@
Feature: `osc enablechannels` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
Scenario: Run `osc enablechannels <project>`
When I execute osc with args "enablechannels test:factory"
Then stdout is
"""
Enabling channels in project 'test:factory'
"""

View File

@ -20,8 +20,9 @@ def before_scenario(context, scenario):
def after_scenario(context, scenario):
if "destructive" in scenario.tags:
# start a new container after a destructive test
context.podman.kill()
context.podman = podman.Podman()
# we must use an existing podman instance defined in `before_all` due to context attribute life-cycle:
# https://behave.readthedocs.io/en/stable/context_attributes.html
context.podman.restart()
context.osc.clear()
common.check_exit_code(context)
@ -46,7 +47,7 @@ def before_all(context):
# absolute path to .../behave/fixtures
context.fixtures = os.path.join(os.path.dirname(__file__), "..", "fixtures")
context.podman = podman.Podman()
context.podman = podman.Podman(context)
context.osc = osc.Osc(context)

View File

@ -0,0 +1,57 @@
Feature: `osc setdevelproject` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "checkout test:factory test-pkgA"
And I set working directory to "{context.osc.temp}/test:factory/test-pkgA"
@destructive
Scenario: Run `osc setdevelproject <devel_project>`
When I execute osc with args "setdevelproject test:devel"
Then the exit code is 0
And stdout is
"""
Setting devel project of package 'test:factory/test-pkgA' to package 'test:devel/test-pkgA'
Sending meta data...
Done.
"""
@destructive
Scenario: Run `osc setdevelproject <devel_project> <devel_package>`
When I execute osc with args "setdevelproject test:devel test-pkgA"
Then the exit code is 0
And stdout is
"""
Setting devel project of package 'test:factory/test-pkgA' to package 'test:devel/test-pkgA'
Sending meta data...
Done.
"""
@destructive
Scenario: Run `osc setdevelproject <devel_project>/<devel_package>`
When I execute osc with args "setdevelproject test:devel/test-pkgA"
Then the exit code is 0
And stdout is
"""
Setting devel project of package 'test:factory/test-pkgA' to package 'test:devel/test-pkgA'
Sending meta data...
Done.
"""
@destructive
Scenario: Run `osc setdevelproject --unset`
Given I execute osc with args "setdevelproject test:devel"
When I execute osc with args "setdevelproject --unset"
Then the exit code is 0
And stdout is
"""
Unsetting devel project from package 'test:factory/test-pkgA'
Sending meta data...
Done.
"""

View File

@ -0,0 +1,17 @@
Feature: `osc setdevelproject` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "checkout test:factory"
And I set working directory to "{context.osc.temp}/test:factory"
Scenario: Run `osc setdevelproject <devel_project>`
When I execute osc with args "setdevelproject devel"
Then the exit code is 1
And stderr is
"""
Directory '{context.osc.temp}/test:factory' is not a working copy of a package
"""

View File

@ -0,0 +1,54 @@
Feature: `osc setdevelproject` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
@destructive
Scenario: Run `osc setdevelproject <project> <package> <devel_project>`
When I execute osc with args "setdevelproject test:factory test-pkgA test:devel"
Then the exit code is 0
And stdout is
"""
Setting devel project of package 'test:factory/test-pkgA' to package 'test:devel/test-pkgA'
Sending meta data...
Done.
"""
@destructive
Scenario: Run `osc setdevelproject <project> <package> <devel_project> <devel_package>`
When I execute osc with args "setdevelproject test:factory test-pkgB test:devel test-pkgA"
Then the exit code is 0
And stdout is
"""
Setting devel project of package 'test:factory/test-pkgB' to package 'test:devel/test-pkgA'
Sending meta data...
Done.
"""
@destructive
Scenario: Run `osc setdevelproject <project>/<package> <devel_project>/<devel_package>`
When I execute osc with args "setdevelproject test:factory/test-pkgB test:devel/test-pkgA"
Then the exit code is 0
And stdout is
"""
Setting devel project of package 'test:factory/test-pkgB' to package 'test:devel/test-pkgA'
Sending meta data...
Done.
"""
@destructive
Scenario: Run `osc setdevelproject <project> <package> --unset`
When I execute osc with args "setdevelproject test:factory test-pkgA --unset"
Then the exit code is 0
And stdout is
"""
Unsetting devel project from package 'test:factory/test-pkgA'
Sending meta data...
Done.
"""

View File

@ -0,0 +1,39 @@
Feature: `osc setlinkrev` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "linkpac test:factory/test-pkgA home:Admin --force"
Scenario: Run `osc setlinkrev <project> <package>`
When I execute osc with args "setlinkrev home:Admin test-pkgA"
Then stdout is
"""
Set link revision of package home:Admin/test-pkgA to 3
"""
Scenario: Run `osc setlinkrev <project>/<package>`
When I execute osc with args "setlinkrev home:Admin/test-pkgA"
Then stdout is
"""
Set link revision of package home:Admin/test-pkgA to 3
"""
Scenario: Run `osc setlinkrev <project> <package> --revision`
When I execute osc with args "setlinkrev home:Admin test-pkgA --revision=2"
Then stdout is
"""
Set link revision of package home:Admin/test-pkgA to 2
"""
Scenario: Run `osc setlinkrev <project> <package> --unset`
When I execute osc with args "setlinkrev home:Admin test-pkgA --unset"
Then stdout is
"""
Removed link revision from package home:Admin/test-pkgA
"""

View File

@ -0,0 +1,33 @@
Feature: `osc setlinkrev` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "linkpac test:factory/test-pkgA home:Admin --force"
Scenario: Run `osc setlinkrev <project>`
When I execute osc with args "setlinkrev home:Admin"
Then stdout is
"""
Set link revision of package home:Admin/test-pkgA to 3
"""
Scenario: Run `osc setlinkrev <project> --revision`
When I execute osc with args "setlinkrev home:Admin --revision=2"
Then the exit code is 2
Scenario: Run `osc setlinkrev <project> --unset`
Given I execute osc with args "setlinkrev home:Admin test-pkgA --revision=2"
And stdout is
"""
Set link revision of package home:Admin/test-pkgA to 2
"""
When I execute osc with args "setlinkrev home:Admin --unset"
Then stdout is
"""
Removed link revision from package home:Admin/test-pkgA
"""

View File

@ -0,0 +1,17 @@
Feature: `osc showlinked` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "checkout test:factory/test-pkgA"
And I set working directory to "{context.osc.temp}/test:factory/test-pkgA"
And I execute osc with args "linkpac test:factory/test-pkgA home:Admin --force"
Scenario: Run `osc showlinked`
When I execute osc with args "showlinked"
Then stdout is
"""
home:Admin/test-pkgA
"""

View File

@ -0,0 +1,17 @@
Feature: `osc showlinked` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "checkout test:factory"
And I set working directory to "{context.osc.temp}/test:factory"
Scenario: Run `osc showlinked`
When I execute osc with args "showlinked"
Then the exit code is 1
And stderr is
"""
Directory '{context.osc.temp}/test:factory' is not a working copy of a package
"""

View File

@ -0,0 +1,23 @@
Feature: `osc showlinked` command
# common steps for all scenarios
Background:
Given I set working directory to "{context.osc.temp}"
And I execute osc with args "linkpac test:factory/test-pkgA home:Admin --force"
Scenario: Run `osc showlinked <project> <package>`
When I execute osc with args "showlinked test:factory test-pkgA"
Then stdout is
"""
home:Admin/test-pkgA
"""
Scenario: Run `osc showlinked <project>/<package>`
When I execute osc with args "showlinked test:factory/test-pkgA"
Then stdout is
"""
home:Admin/test-pkgA
"""

View File

@ -6,6 +6,13 @@ import subprocess
import behave
def debug(context, *args):
if not context.config.userdata.get("DEBUG", False):
return
msg = " ".join((str(i).strip() for i in args))
print(f"DEBUG: {msg}")
def makedirs(path):
try:
os.makedirs(path)
@ -62,16 +69,14 @@ def run_in_context(context, cmd, can_fail=False, **run_args):
env["PATH"] = path.replace("$PATH", env["PATH"])
run_args["env"] = env
if context.config.userdata.get("DEBUG", False):
print(f"DEBUG: command: {cmd}")
debug(context, "Running command:", cmd)
context.cmd_exitcode, context.cmd_stdout, context.cmd_stderr = run(cmd, **run_args)
context.cmd_exitcode_checked = False
if context.config.userdata.get("DEBUG", False):
print(f"DEBUG: exit code: {context.cmd_exitcode}")
print(f"DEBUG: stdout: {context.cmd_stdout}")
print(f"DEBUG: stderr: {context.cmd_stderr}")
debug(context, "> return code:", context.cmd_exitcode)
debug(context, "> stdout:", context.cmd_stdout)
debug(context, "> stderr:", context.cmd_stderr)
if not can_fail and context.cmd_exitcode != 0:
raise AssertionError('Running command "%s" failed: %s' % (cmd, context.cmd_exitcode))
@ -93,8 +98,8 @@ def step_impl(context, text):
@behave.step("stdout is")
def step_impl(context):
expected = context.text.format(context=context).rstrip().split('\n')
found = context.cmd_stdout.rstrip().split('\n')
expected = context.text.format(context=context).rstrip().split("\n")
found = context.cmd_stdout.rstrip().split("\n")
if found == expected:
return
@ -104,6 +109,19 @@ def step_impl(context):
raise AssertionError(f"Stdout is not:\n{expected_str}\n\nActual stdout:\n{found_str}")
@behave.step("stderr is")
def step_impl(context):
expected = context.text.format(context=context).rstrip().split("\n")
found = context.cmd_stderr.rstrip().split("\n")
if found == expected:
return
expected_str = "\n".join(expected)
found_str = "\n".join(found)
raise AssertionError(f"Stderr is not:\n{expected_str}\n\nActual stderr:\n{found_str}")
@behave.step('I set working directory to "{path}"')
def step_impl(context, path):
path = path.format(context=context)
@ -183,7 +201,15 @@ def step_impl(context, path, mode):
@behave.step("the exit code is {exitcode}")
def the_exit_code_is(context, exitcode):
if context.cmd_exitcode != int(exitcode):
raise AssertionError(f"Command has exited with code {context.cmd_exitcode}: {context.cmd}")
lines = [
f"Command has exited with code {context.cmd_exitcode}: {context.cmd}",
"> stdout:",
context.cmd_stdout.strip(),
"",
"> stderr:",
context.cmd_stderr.strip(),
]
raise AssertionError("\n".join(lines))
context.cmd_exitcode_checked = True

View File

@ -1,10 +1,12 @@
import os
import re
import shutil
import tempfile
import time
import behave
from steps.common import debug
from steps.common import run_in_context
@ -14,6 +16,7 @@ class Osc:
raise RuntimeError("context doesn't have 'podman' object set")
self.context = context
debug(self.context, "Osc.__init__()")
self.temp = None
self.clear()
@ -24,6 +27,7 @@ class Osc:
pass
def clear(self):
debug(self.context, "Osc.clear()")
if self.temp:
shutil.rmtree(self.temp)
self.temp = tempfile.mkdtemp(prefix="osc_behave_")
@ -54,6 +58,8 @@ def step_impl(context, args):
cmd = context.osc.get_cmd() + [args]
cmd = " ".join(cmd)
run_in_context(context, cmd, can_fail=True)
# remove InsecureRequestWarning that is irrelevant to the tests
context.cmd_stderr = re.sub(r"^.*InsecureRequestWarning.*\n warnings.warn\(\n", "", context.cmd_stderr)
@behave.step('I wait for osc results for "{project}" "{package}"')

View File

@ -1,12 +1,14 @@
import subprocess
from steps.common import debug
class Podman:
def __init__(self):
def __init__(self, context):
self.context = context
debug(context, "Podman.__init__()")
self.container_id = None
self.run()
self.wait_on_systemd()
self.port = self.get_port()
self.start()
def __del__(self):
try:
@ -16,6 +18,7 @@ class Podman:
def _run(self, args, check=True):
cmd = ["podman"] + args
debug(self.context, "Running command:", cmd)
proc = subprocess.run(
cmd,
stdout=subprocess.PIPE,
@ -23,9 +26,13 @@ class Podman:
encoding="utf-8",
check=check,
)
debug(self.context, "> return code:", proc.returncode)
debug(self.context, "> stdout:", proc.stdout)
debug(self.context, "> stderr:", proc.stderr)
return proc
def run(self):
def start(self):
debug(self.context, "Podman.start()")
args = [
"run",
"--name", "obs-server-behave",
@ -41,14 +48,22 @@ class Podman:
proc = self._run(args)
lines = proc.stdout.strip().splitlines()
self.container_id = lines[-1]
self.wait_on_systemd()
self.port = self.get_port()
def kill(self):
if not self.container_id:
return
debug(self.context, "Podman.kill()")
args = ["kill", self.container_id]
self._run(args)
self.container_id = None
def restart(self):
debug(self.context, "Podman.restart()")
self.kill()
self.start()
def wait_on_systemd(self):
args = [
"exec",
@ -65,4 +80,4 @@ class Podman:
if line.startswith("443/tcp"):
# return <port> from: "443/tcp -> 0.0.0.0:<port>"
return line.split(":")[-1]
return None
raise RuntimeError(f"Could not determine port of container {self.container_id}")

View File

@ -3,5 +3,12 @@
#
# The cherry-picked imports will be the supported API.
from .api_source import add_channels
from .api_source import add_containers
from .api_source import enable_channels
from .api_source import get_linked_packages
from .api_source import release
from .common import print_msg
from .common import format_msg_project_package_options
from .package import ApiPackage
from .request import forward_request

View File

@ -33,6 +33,31 @@ def get(apiurl, path, query=None):
return root
def post(apiurl, path, query=None):
"""
Send a POST request to OBS.
:param apiurl: OBS apiurl.
:type apiurl: str
:param path: URL path segments.
:type path: list(str)
:param query: URL query values.
:type query: dict(str, str)
:returns: Parsed XML root.
:rtype: xml.etree.ElementTree.Element
"""
assert apiurl
assert path
if not isinstance(path, (list, tuple)):
raise TypeError("Argument `path` expects a list of strings")
url = osc_core.makeurl(apiurl, path, query)
with osc_connection.http_POST(url) as f:
root = osc_core.ET.parse(f).getroot()
return root
def find_nodes(root, root_name, node_name):
"""
Find nodes with given `node_name`.

126
osc/_private/api_source.py Normal file
View File

@ -0,0 +1,126 @@
from . import api
from .common import format_msg_project_package_options
from .common import print_msg
from .. import oscerr
def add_channels(apiurl, project, package=None, enable_all=False, skip_disabled=False, print_to="debug"):
if all((enable_all, skip_disabled)):
raise oscerr.OscValueError("Options 'enable_all' and 'skip_disabled' are mutually exclusive")
msg = format_msg_project_package_options(
"Adding channels to",
project,
package,
enable_all=enable_all,
skip_disabled=skip_disabled,
)
print_msg(msg, print_to=print_to)
url_path = ["source", project]
if package:
url_path += [package]
url_query = {"cmd": "addchannels"}
if enable_all:
url_query["mode"] = "enable_all"
if skip_disabled:
url_query["mode"] = "skip_disabled"
return api.post(apiurl, url_path, url_query)
def add_containers(apiurl, project, package, extend_package_names=False, print_to="debug"):
msg = format_msg_project_package_options(
"Adding containers to",
project,
package,
extend_package_names=extend_package_names,
)
print_msg(msg, print_to=print_to)
url_path = ["source", project, package]
url_query = {"cmd": "addcontainers"}
if extend_package_names:
url_query["extend_package_names"] = "1"
return api.post(apiurl, url_path, url_query)
def enable_channels(apiurl, project, package=None, print_to="debug"):
msg = format_msg_project_package_options(
"Enabling channels in",
project,
package,
)
print_msg(msg, print_to=print_to)
url_path = ["source", project]
if package:
url_path += [package]
if package:
url_query = {"cmd": "enablechannel"}
else:
url_query = {"cmd": "modifychannels", "mode": "enable_all"}
return api.post(apiurl, url_path, url_query)
def get_linked_packages(apiurl, project, package):
url_path = ["source", project, package]
url_query = {"cmd": "showlinked"}
root = api.post(apiurl, url_path, url_query)
result = []
nodes = api.find_nodes(root, "collection", "package")
for node in nodes:
item = {
"project": node.get("project"),
"name": node.get("name"),
}
result.append(item)
return result
def release(
apiurl,
project,
package,
repository,
target_project,
target_repository,
set_release_to=None,
delayed=False,
print_to="debug",
):
msg = format_msg_project_package_options(
"Releasing",
project,
package,
target_project,
target_package=None,
repository=repository,
dest_repository=target_repository,
delayed=delayed,
)
print_msg(msg, print_to=print_to)
url_path = ["source", project]
if package:
url_path += [package]
url_query = {"cmd": "release"}
if repository:
url_query["repository"] = repository
if target_project:
url_query["target_project"] = target_project
if target_repository:
url_query["target_repository"] = target_repository
if set_release_to:
url_query["setrelease"] = set_release_to
if not delayed:
url_query["nodelay"] = "1"
return api.post(apiurl, url_path, url_query)

57
osc/_private/common.py Normal file
View File

@ -0,0 +1,57 @@
import sys
def print_msg(msg, print_to="debug"):
from .. import conf
if print_to is None:
return
elif print_to == "debug":
if conf.config["debug"]:
print(f"DEBUG: {msg}", file=sys.stderr)
elif print_to == "stdout":
print(msg)
else:
raise ValueError(f"Invalid value of the 'output' option: {output}")
def format_msg_project_package_options(
msg,
project=None,
package=None,
dest_project=None,
dest_package=None,
repository=None,
dest_repository=None,
**options,
):
"""
Format msg, project, package, dest_project, dest_package and options into a meaningful message
that can be printed out directly or as a debug message.
"""
if project and not package:
msg += f" project '{project}'"
else:
msg += f" package '{project}/{package}'"
if repository:
msg += f" repository '{repository}'"
if any([dest_project, dest_package, dest_repository]):
msg += " to"
if dest_project and not dest_package:
msg += f" project '{dest_project}'"
elif dest_project and dest_package:
msg += f" package '{dest_project}/{dest_package}'"
if dest_repository:
msg += f" repository '{dest_repository}'"
msg_options = [key.replace("_", "-") for key, value in options.items() if value]
if msg_options:
msg_options.sort()
msg_options_str = ", ".join(msg_options)
msg += f" options: {msg_options_str}"
return msg

View File

@ -48,6 +48,87 @@ def get_parser():
return osc.argparser
def pop_project_package_from_args(args, default_project=None, default_package=None, package_is_optional=False):
"""
Get project and package from given `args`.
:param args: List of command-line arguments.
WARNING: `args` gets modified in this function call!
:type args: list(str)
:param default_project: Used if project cannot be retrieved from `args`.
Resolved from the current working copy if set to '.'.
:type default_project: str
:param default_package: Used if package cannot be retrieved from `args`.
Resolved from the current working copy if set to '.'.
:type default_package: str
:param package_is_optional: Whether to error out when package name cannot be retrieved.
:type package_is_optional: bool
:returns: Project name and package name.
:rtype: tuple(str)
"""
assert isinstance(args, list)
path = Path.cwd()
used_default_project = False
try:
project = args.pop(0)
except IndexError:
if not default_project:
raise oscerr.OscValueError("Please specify a project")
project = default_project
used_default_project = True
if not isinstance(project, str):
raise TypeError(f"Project should be 'str', found: {type(project).__name__}")
package = None
if project == "/":
# no project name (to support listing all projects via `osc ls /`)
project = None
elif project and "/" in project:
# project/package
if project.count("/") != 1:
raise oscerr.OscValueError(f"Argument doesn't match the '<project>/<package>' pattern: {project}")
project, package = project.split("/")
if project == ".":
# project name taken from the working copy
store = osc_store.Store(path)
project = store.project
if package is None:
try:
package = args.pop(0)
except IndexError:
if not package_is_optional and not used_default_project:
# package is not optional and it wasn't specified together with the project
raise oscerr.OscValueError("Please specify a package")
if default_package:
package = default_package
else:
if package_is_optional:
return project, None
raise oscerr.OscValueError("Please specify a package")
if not isinstance(package, str):
raise TypeError(f"Package should be 'str', found: {type(package).__name__}")
if package == ".":
# package name taken from the working copy
try:
store = osc_store.Store(path)
store.assert_is_package()
package = store.package
except oscerr.NoWorkingCopy:
if not package_is_optional:
raise
package = None
return project, package
class Osc(cmdln.Cmdln):
"""
openSUSE commander is a command-line interface to the Open Build Service.
@ -514,28 +595,16 @@ class Osc(cmdln.Cmdln):
osc addcontainers [PROJECT PACKAGE]
"""
args = slash_split(args)
apiurl = self.get_api_url()
localdir = Path.cwd()
project = package = None
if not args:
if is_package_dir(localdir):
project = store_read_project(localdir)
package = store_read_package(localdir)
elif len(args) == 2:
project = self._process_project_name(args[0])
package = args[1]
if project is None or package is None:
raise oscerr.WrongArgs('Either specify project and package or call it from a package working copy')
args = list(args)
project, package = pop_project_package_from_args(
args, default_project=".", default_package=".", package_is_optional=False
)
query = {'cmd': 'addcontainers'}
if opts.extend_package_names:
query['extend_package_names'] = '1'
print("Add containers...")
url = makeurl(apiurl, ['source', project, package], query=query)
f = http_POST(url)
_private.add_containers(
apiurl, project, package, extend_package_names=opts.extend_package_names, print_to="stdout"
)
@cmdln.option('-s', '--skip-disabled', action='store_true',
help='Skip disabled channels. Otherwise the source gets added, but not the repositories.')
@ -554,36 +623,19 @@ class Osc(cmdln.Cmdln):
Examples:
osc addchannels [PROJECT [PACKAGE]]
"""
args = slash_split(args)
apiurl = self.get_api_url()
localdir = Path.cwd()
channel = None
if not args:
if is_project_dir(localdir) or is_package_dir(localdir):
project = store_read_project(localdir)
elif is_package_dir(localdir):
project = store_read_project(localdir)
channel = store_read_package(localdir)
else:
raise oscerr.WrongArgs('Either specify project [package] or call it from a project/package working copy')
else:
project = self._process_project_name(args[0])
query = {'cmd': 'addchannels'}
args = list(args)
project, package = pop_project_package_from_args(
args, default_project=".", default_package=".", package_is_optional=True
)
if opts.enable_all and opts.skip_disabled:
raise oscerr.WrongOptions('--enable-all and --skip-disabled options are mutually exclusive')
elif opts.enable_all:
query['mode'] = 'enable_all'
elif opts.skip_disabled:
query['mode'] = 'skip_disabled'
self.argparse_error("Options '--enable-all' and '--skip-disabled' are mutually exclusive")
print("Looking for channels...")
url = makeurl(apiurl, ['source', project], query=query)
if channel:
url = makeurl(apiurl, ['source', project, channel], query=query)
f = http_POST(url)
_private.add_channels(
apiurl, project, package, enable_all=opts.enable_all, skip_disabled=opts.skip_disabled, print_to="stdout"
)
@cmdln.alias('enablechannel')
def do_enablechannels(self, subcmd, opts, *args):
@ -595,37 +647,16 @@ class Osc(cmdln.Cmdln):
The command can be used to enable a specific one or all channels of a project.
Examples:
osc enablechannels [PROJECT [CHANNEL_PACKAGE]]
osc enablechannels [PROJECT [PACKAGE]]
"""
args = slash_split(args)
apiurl = self.get_api_url()
localdir = Path.cwd()
channel = None
if not args:
if is_project_dir(localdir):
project = store_read_project(localdir)
elif is_package_dir(localdir):
project = store_read_project(localdir)
channel = store_read_package(localdir)
else:
raise oscerr.WrongArgs('Either specify project [package] or call it from a project/package working copy')
else:
project = self._process_project_name(args[0])
if len(args) > 1:
channel = args[1]
query = {}
if channel:
query['cmd'] = 'enablechannel'
else:
query = {'cmd': 'modifychannels', 'mode': 'enable_all'}
args = list(args)
project, package = pop_project_package_from_args(
args, default_project=".", default_package=".", package_is_optional=True
)
print("Enable channel(s)...")
url = makeurl(apiurl, ['source', project], query=query)
if channel:
url = makeurl(apiurl, ['source', project, channel], query=query)
f = http_POST(url)
_private.enable_channels(apiurl, project, package, print_to="stdout")
@cmdln.option('-f', '--force', action='store_true',
help='force generation of new patchinfo file, do not update existing one.')
@ -711,28 +742,22 @@ class Osc(cmdln.Cmdln):
Print the devel project / package of a package
Examples:
osc develproject PRJ PKG
osc develproject
osc develproject [PROJECT PACKAGE]
"""
args = slash_split(args)
apiurl = self.get_api_url()
if len(args) == 0:
project = store_read_project(Path.cwd())
package = store_read_package(Path.cwd())
elif len(args) == 2:
project = self._process_project_name(args[0])
package = args[1]
else:
raise oscerr.WrongArgs('need Project and Package')
args = list(args)
project, package = pop_project_package_from_args(
args, default_project=".", default_package=".", package_is_optional=False
)
devprj, devpkg = show_devel_project(apiurl, project, package)
if devprj is None:
print('%s / %s has no devel project' % (project, package))
elif devpkg and devpkg != package:
print("%s %s" % (devprj, devpkg))
else:
print(devprj)
devel_project, devel_package = show_devel_project(apiurl, project, package)
if not devel_project:
print(f"Package {project}/{package} has no devel project", file=sys.stderr)
sys.exit(1)
print(f"{devel_project}/{devel_package}")
@cmdln.alias('ca')
def do_cleanassets(self, subcmd, opts, *args):
@ -757,29 +782,41 @@ class Osc(cmdln.Cmdln):
"""Set the devel project / package of a package
Examples:
osc setdevelproject [PRJ PKG] DEVPRJ [DEVPKG]
osc setdevelproject [PROJECT PACKAGE] DEVEL_PROJECT [DEVEL_PACKAGE]
"""
args = slash_split(args)
apiurl = self.get_api_url()
devprj, devpkg = None, None
if len(args) == 3 or len(args) == 4:
project, package = self._process_project_name(args[0]), args[1]
devprj = self._process_project_name(args[2])
if len(args) == 4:
devpkg = args[3]
elif len(args) >= 1 and len(args) <= 2:
project, package = store_read_project(Path.cwd()), store_read_package(Path.cwd())
devprj = self._process_project_name(args[0])
if len(args) == 2:
devpkg = args[1]
args = list(args)
if opts.unset:
project, package = pop_project_package_from_args(
args, default_project=".", default_package=".", package_is_optional=False
)
devel_project = None
devel_package = None
else:
if opts.unset:
project, package = store_read_project(Path.cwd()), store_read_package(Path.cwd())
else:
raise oscerr.WrongArgs('need at least DEVPRJ (and possibly DEVPKG)')
args_backup = args.copy()
set_devel_project(apiurl, project, package, devprj, devpkg)
try:
# try this sequence first: project package devel_project [devel_package]
project, package = pop_project_package_from_args(args, package_is_optional=False)
devel_project, devel_package = pop_project_package_from_args(
args, default_package=package, package_is_optional=True
)
except oscerr.OscValueError:
# then read project and package from working copy and try devel_project [devel_package]
args = args_backup.copy()
project, package = pop_project_package_from_args(
[], default_project=".", default_package=".", package_is_optional=False
)
devel_project, devel_package = pop_project_package_from_args(
args, default_package=package, package_is_optional=True
)
if args:
args_str = ", ".join(args)
self.argparse_error(f"Unknown arguments: {args_str}")
set_devel_project(apiurl, project, package, devel_project, devel_package, print_to="stdout")
def do_showlinked(self, subcmd, opts, *args):
"""
@ -789,24 +826,16 @@ class Osc(cmdln.Cmdln):
osc showlinked [PROJECT PACKAGE]
"""
args = slash_split(args)
apiurl = self.get_api_url()
localdir = Path.cwd()
project = package = None
if len(args) == 2:
project = self._process_project_name(args[0])
package = args[1]
elif is_package_dir(localdir):
project = store_read_project(localdir)
package = store_read_package(localdir)
else:
raise oscerr.WrongArgs('Either specify project and package or call it from a package working copy')
url = makeurl(apiurl, ['source', project, package], query={'cmd': 'showlinked'})
f = http_POST(url)
root = ET.parse(f).getroot()
for node in root.findall('package'):
print(node.get('project') + " " + node.get('name'))
args = list(args)
project, package = pop_project_package_from_args(
args, default_project=".", default_package=".", package_is_optional=False
)
linked_packages = _private.get_linked_packages(apiurl, project, package)
for pkg in linked_packages:
print(f"{pkg['project']}/{pkg['name']}")
@cmdln.option('-c', '--create', action='store_true',
help='Create a new token')
@ -2744,27 +2773,28 @@ Please submit there instead, or use --nodevelproject to force direct submission.
osc setlinkrev PROJECT [PACKAGE]
"""
args = slash_split(args)
apiurl = self.get_api_url()
package = None
rev = parseRevisionOption(opts.revision)[0] or ''
if opts.unset:
rev = None
args = list(args)
if not args:
p = Package(Path.cwd())
project = p.prjname
package = p.name
apiurl = p.apiurl
assert apiurl == p.apiurl
if not p.islink():
sys.exit('Local directory is no checked out source link package, aborting')
elif len(args) == 2:
project = self._process_project_name(args[0])
package = args[1]
elif len(args) == 1:
project = self._process_project_name(args[0])
else:
self.argparse_error("Incorrect number of arguments.")
project, package = pop_project_package_from_args(
args, default_project=".", default_package=".", package_is_optional=True
)
if opts.revision and not package:
# It is highly unlikely that all links for all packages in a project should be set to the same revision.
self.argparse_error("The --revision option requires to specify a package")
if package:
packages = [package]
@ -2772,12 +2802,18 @@ Please submit there instead, or use --nodevelproject to force direct submission.
packages = meta_get_packagelist(apiurl, project)
for p in packages:
rev = set_link_rev(apiurl, project, p, revision=rev,
expand=not opts.use_plain_revision)
try:
rev = set_link_rev(apiurl, project, p, revision=rev, expand=not opts.use_plain_revision)
except HTTPError as e:
if e.code != 404:
raise
print(f"WARNING: Package {project}/{p} has no link", file=sys.stderr)
continue
if rev is None:
print('removed revision from link')
print(f"Removed link revision from package {project}/{p}")
else:
print('set revision to %s for package %s' % (rev, p))
print(f"Set link revision of package {project}/{p} to {rev}")
def do_linktobranch(self, subcmd, opts, *args):
"""
@ -3100,51 +3136,26 @@ Please submit there instead, or use --nodevelproject to force direct submission.
It requires defined release targets set to trigger="manual".
usage:
osc release [ SOURCEPROJECT [ SOURCEPACKAGE ] ]
osc release [PROJECT [PACKAGE]]
"""
args = slash_split(args)
apiurl = self.get_api_url()
source_project = source_package = None
args = list(args)
project, package = pop_project_package_from_args(
args, default_project=".", default_package=".", package_is_optional=True
)
if len(args) > 2:
raise oscerr.WrongArgs('Too many arguments.')
if len(args) == 0:
if is_package_dir(Path.cwd()):
source_project = store_read_project(Path.cwd())
source_package = store_read_package(Path.cwd())
elif is_project_dir(Path.cwd()):
source_project = store_read_project(Path.cwd())
else:
raise oscerr.WrongArgs('Too few arguments.')
if len(args) > 0:
source_project = self._process_project_name(args[0])
if len(args) > 1:
source_package = args[1]
query = {'cmd': 'release'}
if opts.target_project:
query["target_project"] = opts.target_project
if opts.target_repository:
query["target_repository"] = opts.target_repository
if opts.repo:
query["repository"] = opts.repo
if opts.set_release:
query["setrelease"] = opts.set_release
if opts.no_delay:
query["nodelay"] = "1"
baseurl = ['source', source_project]
if source_package:
baseurl.append(source_package)
url = makeurl(apiurl, baseurl, query=query)
f = http_POST(url)
while True:
buf = f.read(16384)
if not buf:
break
sys.stdout.write(decode_it(buf))
_private.release(
apiurl,
project=project,
package=package,
repository=opts.repo,
target_project=opts.target_project,
target_repository=opts.target_repository,
set_release_to=opts.set_release,
delayed=not opts.no_delay,
print_to="stdout",
)
@cmdln.option('-m', '--message', metavar='TEXT',
help='specify message TEXT')
@ -6827,30 +6838,12 @@ Please submit there instead, or use --nodevelproject to force direct submission.
osc log (inside working copy)
osc log remote_project [remote_package]
"""
args = slash_split(args)
apiurl = self.get_api_url()
if len(args) == 0:
wd = Path.cwd()
if is_project_dir(wd) or is_package_dir(wd):
project = store_read_project(wd)
if is_project_dir(wd):
package = "_project"
else:
package = store_read_package(wd)
else:
raise oscerr.NoWorkingCopy("Error: \"%s\" is not an osc working copy." % os.path.abspath(wd))
elif len(args) < 1:
raise oscerr.WrongArgs('Too few arguments (required none or two)')
elif len(args) > 2:
raise oscerr.WrongArgs('Too many arguments (required none or two)')
elif len(args) == 1:
project = self._process_project_name(args[0])
package = "_project"
else:
project = self._process_project_name(args[0])
package = args[1]
args = list(args)
project, package = pop_project_package_from_args(
args, default_project=".", default_package=".", package_is_optional=True
)
rev, rev_upper = parseRevisionOption(opts.revision)
if rev and not checkRevision(project, package, rev, apiurl, opts.meta):

View File

@ -43,6 +43,7 @@ except ImportError:
distro = None
from . import __version__
from . import _private
from . import conf
from . import meter
from . import oscerr
@ -3688,7 +3689,21 @@ def show_devel_project(apiurl, prj, pac):
return node.get('project'), node.get('package', None)
def set_devel_project(apiurl, prj, pac, devprj=None, devpac=None):
def set_devel_project(apiurl, prj, pac, devprj=None, devpac=None, print_to="debug"):
if devprj:
msg = "Setting devel project of"
else:
msg = "Unsetting devel project from"
msg = _private.format_msg_project_package_options(
msg,
prj,
pac,
devprj,
devpac,
)
_private.print_msg(msg, print_to=print_to)
meta = show_package_meta(apiurl, prj, pac)
root = ET.fromstring(b''.join(meta))
node = root.find('devel')
@ -6866,6 +6881,9 @@ def print_jobhistory(apiurl: str, prj: str, current_package: str, repository: st
def get_commitlog(
apiurl: str, prj: str, package: str, revision, format="text", meta=False, deleted=False, revision_upper=None
):
if package is None:
package = "_project"
query = {}
if deleted:
query['deleted'] = 1

144
tests/test_commandline.py Normal file
View File

@ -0,0 +1,144 @@
import os
import shutil
import tempfile
import unittest
from osc.commandline import pop_project_package_from_args
from osc.oscerr import NoWorkingCopy, OscValueError
from osc.store import Store
class TestPopProjectPackageFromArgs(unittest.TestCase):
def _write_store(self, project=None, package=None):
store = Store(self.tmpdir, check=False)
if project:
store.project = project
store.is_project = True
if package:
store.package = package
store.is_project = False
store.is_package = True
def setUp(self):
self.tmpdir = tempfile.mkdtemp(prefix="osc_test")
os.chdir(self.tmpdir)
def tearDown(self):
try:
shutil.rmtree(self.tmpdir)
except OSError:
pass
def test_explicit_project_and_package(self):
args = ["project", "package", "another-arg"]
project, package = pop_project_package_from_args(args)
self.assertEqual(project, "project")
self.assertEqual(package, "package")
self.assertEqual(args, ["another-arg"])
def test_defaults(self):
args = ["project"]
self.assertRaises(OscValueError, pop_project_package_from_args, args, default_package="default-package")
args = ["project"]
project, package = pop_project_package_from_args(
args, default_package="default-package", package_is_optional=True
)
self.assertEqual(project, "project")
self.assertEqual(package, "default-package")
self.assertEqual(args, [])
args = []
project, package = pop_project_package_from_args(
args, default_project="default-project", default_package="default-package"
)
self.assertEqual(project, "default-project")
self.assertEqual(package, "default-package")
self.assertEqual(args, [])
args = []
project, package = pop_project_package_from_args(
args, default_project="default-project", default_package="default-package", package_is_optional=True
)
self.assertEqual(project, "default-project")
self.assertEqual(package, "default-package")
self.assertEqual(args, [])
def test_slash_separator(self):
args = ["project/package", "another-arg"]
project, package = pop_project_package_from_args(args)
self.assertEqual(project, "project")
self.assertEqual(package, "package")
self.assertEqual(args, ["another-arg"])
args = ["project/", "another-arg"]
project, package = pop_project_package_from_args(args)
self.assertEqual(project, "project")
self.assertEqual(package, "")
self.assertEqual(args, ["another-arg"])
def test_no_working_copy(self):
args = [".", "."]
self.assertRaises(NoWorkingCopy, pop_project_package_from_args, args)
args = [".", "package"]
self.assertRaises(NoWorkingCopy, pop_project_package_from_args, args)
args = ["project", "."]
self.assertRaises(NoWorkingCopy, pop_project_package_from_args, args)
def test_project_and_package_from_project_working_copy(self):
self._write_store("store_project")
args = [".", ".", "another-arg"]
self.assertRaises(NoWorkingCopy, pop_project_package_from_args, args)
args = ["."]
project, package = pop_project_package_from_args(args, package_is_optional=True)
self.assertEqual(project, "store_project")
self.assertEqual(package, None)
self.assertEqual(args, [])
args = []
self.assertRaises(NoWorkingCopy, pop_project_package_from_args, args, default_project=".", default_package=".")
args = []
project, package = pop_project_package_from_args(
args, default_project=".", default_package=".", package_is_optional=True
)
self.assertEqual(project, "store_project")
self.assertEqual(package, None)
self.assertEqual(args, [])
def test_project_and_package_from_package_working_copy(self):
self._write_store("store_project", "store_package")
args = [".", ".", "another-arg"]
project, package = pop_project_package_from_args(args)
self.assertEqual(project, "store_project")
self.assertEqual(package, "store_package")
self.assertEqual(args, ["another-arg"])
args = ["."]
project, package = pop_project_package_from_args(args, package_is_optional=True)
self.assertEqual(project, "store_project")
self.assertEqual(package, None)
self.assertEqual(args, [])
args = []
project, package = pop_project_package_from_args(args, default_project=".", default_package=".")
self.assertEqual(project, "store_project")
self.assertEqual(package, "store_package")
self.assertEqual(args, [])
args = []
project, package = pop_project_package_from_args(
args, default_project=".", default_package=".", package_is_optional=True
)
self.assertEqual(project, "store_project")
self.assertEqual(package, "store_package")
self.assertEqual(args, [])
if __name__ == "__main__":
unittest.main()