1
0
mirror of https://github.com/openSUSE/osc.git synced 2025-02-15 23:07:18 +01:00

Migrate 'token' command to obs_api.Token

This commit is contained in:
Daniel Mach 2024-04-26 22:15:15 +02:00
parent 3c096b82c9
commit 011adb4689
5 changed files with 220 additions and 43 deletions

View File

@ -5,7 +5,6 @@ Scenario: Run `osc token` with no arguments
When I execute osc with args "token" When I execute osc with args "token"
Then stdout is Then stdout is
""" """
<directory count="0"/>
""" """
@ -24,11 +23,15 @@ Scenario: Run `osc token --operation rebuild`
Given I execute osc with args "token" Given I execute osc with args "token"
And stdout matches And stdout matches
""" """
<directory count="1"> ID : 1
<entry id="1" string=".*" kind="rebuild" description="" triggered_at="" project="test:factory" package="test-pkgA"/> String : .*
</directory> Operation : rebuild
Description :
Project : test:factory
Package : test-pkgA
Triggered at :
""" """
And I search 'string="(?P<token>[^"]+)' in stdout and store named groups in 'tokens' And I search 'String *: *(?P<token>.+)\n' in stdout and store named groups in 'tokens'
When I execute osc with args "token --trigger {context.tokens[0][token]}" When I execute osc with args "token --trigger {context.tokens[0][token]}"
Then stdout is Then stdout is
""" """

View File

@ -1688,6 +1688,7 @@ class Osc(cmdln.Cmdln):
osc token --delete <TOKENID> osc token --delete <TOKENID>
osc token --trigger <TOKENSTRING> [--operation <OPERATION>] [<PROJECT> <PACKAGE>] osc token --trigger <TOKENSTRING> [--operation <OPERATION>] [<PROJECT> <PACKAGE>]
""" """
from . import obs_api
args = slash_split(args) args = slash_split(args)
@ -1696,60 +1697,51 @@ class Osc(cmdln.Cmdln):
raise oscerr.WrongOptions(msg) raise oscerr.WrongOptions(msg)
apiurl = self.get_api_url() apiurl = self.get_api_url()
url_path = ['person', conf.get_apiurl_usr(apiurl), 'token'] user = conf.get_apiurl_usr(apiurl)
if len(args) > 1:
project = args[0]
package = args[1]
else:
project = None
package = None
if opts.create: if opts.create:
if not opts.operation: if not opts.operation:
self.argparser.error("Please specify --operation") self.argparser.error("Please specify --operation")
if opts.operation == 'workflow' and not opts.scm_token: if opts.operation == 'workflow' and not opts.scm_token:
msg = 'The --operation=workflow option requires a --scm-token=<token> option' msg = 'The --operation=workflow option requires a --scm-token=<token> option'
raise oscerr.WrongOptions(msg) raise oscerr.WrongOptions(msg)
print("Create a new token")
query = {'cmd': 'create'}
if opts.operation:
query['operation'] = opts.operation
if opts.scm_token:
query['scm_token'] = opts.scm_token
if len(args) > 1:
query['project'] = args[0]
query['package'] = args[1]
url = makeurl(apiurl, url_path, query) print("Create a new token")
f = http_POST(url) status = obs_api.Token.cmd_create(
while True: apiurl,
data = f.read(16384) user,
if not data: operation=opts.operation,
break project=project,
sys.stdout.buffer.write(data) package=package,
scm_token=opts.scm_token,
)
print(status.to_string())
elif opts.delete: elif opts.delete:
print("Delete token") print("Delete token")
url_path.append(opts.delete) status = obs_api.Token.do_delete(apiurl, user, token=opts.delete)
url = makeurl(apiurl, url_path) print(status.to_string())
http_DELETE(url)
elif opts.trigger: elif opts.trigger:
print("Trigger token") print("Trigger token")
query = {} status = obs_api.Token.do_trigger(apiurl, token=opts.trigger, project=project, package=package)
if len(args) > 1: print(status.to_string())
query['project'] = args[0]
query['package'] = args[1]
if opts.operation:
url = makeurl(apiurl, ["trigger", opts.operation], query)
else:
url = makeurl(apiurl, ["trigger"], query)
headers = {
'Content-Type': 'application/octet-stream',
'Authorization': "Token " + opts.trigger,
}
fd = http_POST(url, headers=headers)
print(decode_it(fd.read()))
else: else:
if args and args[0] in ['create', 'delete', 'trigger']: if args and args[0] in ['create', 'delete', 'trigger']:
raise oscerr.WrongArgs("Did you mean --" + args[0] + "?") raise oscerr.WrongArgs("Did you mean --" + args[0] + "?")
# just list token
url = makeurl(apiurl, url_path) # just list tokens
for data in streamfile(url, http_GET): token_list = obs_api.Token.do_list(apiurl, user)
sys.stdout.buffer.write(data) for obj in token_list:
print(obj.to_human_readable_string())
print()
@cmdln.option('-a', '--attribute', metavar='ATTRIBUTE', @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
help='affect only a given attribute') help='affect only a given attribute')

View File

@ -4,3 +4,4 @@ from .package_sources import PackageSources
from .person import Person from .person import Person
from .project import Project from .project import Project
from .request import Request from .request import Request
from .token import Token

View File

@ -11,6 +11,8 @@ class StatusData(XmlModel):
SOURCEPACKAGE = "sourcepackage" SOURCEPACKAGE = "sourcepackage"
TARGETPROJECT = "targetproject" TARGETPROJECT = "targetproject"
TARGETPACKAGE = "targetpackage" TARGETPACKAGE = "targetpackage"
TOKEN = "token"
ID = "id"
name: NameEnum = Field( name: NameEnum = Field(
xml_attribute=True, xml_attribute=True,

179
osc/obs_api/token.py Normal file
View File

@ -0,0 +1,179 @@
import textwrap
from ..util.models import * # pylint: disable=wildcard-import,unused-wildcard-import
from .status import Status
class Token(XmlModel):
XML_TAG = "entry"
id: int = Field(
xml_attribute=True,
description=textwrap.dedent(
"""
The unique id of this token.
"""
),
)
string: str = Field(
xml_attribute=True,
description=textwrap.dedent(
"""
The token secret. This string can be used instead of the password to
authenticate the user or to trigger service runs via the
`POST /trigger/runservice` route.
"""
),
)
description: Optional[str] = Field(
xml_attribute=True,
description=textwrap.dedent(
"""
This attribute can be used to identify a token from the list of tokens
of a user.
"""
),
)
project: Optional[str] = Field(
xml_attribute=True,
description=textwrap.dedent(
"""
If this token is bound to a specific package, then the packages'
project is available in this attribute.
"""
),
)
package: Optional[str] = Field(
xml_attribute=True,
description=textwrap.dedent(
"""
The package name to which this token is bound, if it has been created
for a specific package. Otherwise this attribute and the project
attribute are omitted.
"""
),
)
class Kind(str, Enum):
RSS = "rss"
REBUILD = "rebuild"
RELEASE = "release"
RUNSERVICE = "runservice"
WORKFLOW = "workflow"
kind: Kind = Field(
xml_attribute=True,
description=textwrap.dedent(
"""
This attribute specifies which actions can be performed via this token.
- rss: used to retrieve the notification RSS feed
- rebuild: trigger rebuilds of packages
- release: trigger project releases
- runservice: run a service via the POST /trigger/runservice route
- workflow: trigger SCM/CI workflows, see https://openbuildservice.org/help/manuals/obs-user-guide/cha.obs.scm_ci_workflow_integration.html
"""
),
)
triggered_at: str = Field(
xml_attribute=True,
description=textwrap.dedent(
"""
The date and time a token got triggered the last time.
"""
),
)
def to_human_readable_string(self) -> str:
"""
Render the object as a human readable string.
"""
from ..output import KeyValueTable
table = KeyValueTable()
table.add("ID", str(self.id))
table.add("String", self.string, color="bold")
table.add("Operation", self.kind)
table.add("Description", self.description)
table.add("Project", self.project)
table.add("Package", self.package)
table.add("Triggered at", self.triggered_at)
return f"{table}"
@classmethod
def do_list(cls, apiurl: str, user: str):
from ..util.xml import ET
url_path = ["person", user, "token"]
url_query = {}
response = cls.xml_request("GET", apiurl, url_path, url_query)
root = ET.parse(response).getroot()
assert root.tag == "directory"
result = []
for node in root:
result.append(cls.from_xml(node, apiurl=apiurl))
return result
@classmethod
def cmd_create(
cls,
apiurl: str,
user: str,
*,
operation: Optional[str] = None,
project: Optional[str] = None,
package: Optional[str] = None,
scm_token: Optional[str] = None,
):
if operation == "workflow" and not scm_token:
raise ValueError('``operation`` = "workflow" requires ``scm_token``')
url_path = ["person", user, "token"]
url_query = {
"cmd": "create",
"operation": operation,
"project": project,
"package": package,
"scm_token": scm_token,
}
response = cls.xml_request("POST", apiurl, url_path, url_query)
return Status.from_file(response, apiurl=apiurl)
@classmethod
def do_delete(cls, apiurl: str, user: str, token: str):
url_path = ["person", user, "token", token]
url_query = {}
response = cls.xml_request("DELETE", apiurl, url_path, url_query)
return Status.from_file(response, apiurl=apiurl)
@classmethod
def do_trigger(
cls,
apiurl: str,
token: str,
*,
operation: Optional[str] = None,
project: Optional[str] = None,
package: Optional[str] = None,
):
if operation:
url_path = ["trigger", operation]
else:
url_path = ["trigger"]
url_query = {
"project": project,
"package": package,
}
headers = {
"Content-Type": "application/octet-stream",
"Authorization": f"Token {token}",
}
response = cls.xml_request("POST", apiurl, url_path, url_query, headers=headers)
return Status.from_file(response, apiurl=apiurl)