Migrating trigger functionality

Migrating trigger functionality to work with the app and
workflow resources instead of the plan and pipeline resources.

Change-Id: I5674c6dfc4e7d7813480cb8c9108012a5838dd22
Closes-Bug: #1495693
This commit is contained in:
Devdatta Kulkarni 2015-09-17 17:22:55 -05:00
parent 4d469df2d9
commit 706b1e1253
12 changed files with 96 additions and 99 deletions

View File

@ -16,6 +16,8 @@ import requests
import yaml import yaml
from functionaltests.api import base from functionaltests.api import base
from functionaltests.api.common import apputils
assembly_data = {'name': 'test_assembly', assembly_data = {'name': 'test_assembly',
'description': 'desc assembly'} 'description': 'desc assembly'}
@ -53,8 +55,15 @@ class TestTriggerController(base.TestCase):
self.assertIsNotNone(trigger_uri) self.assertIsNotNone(trigger_uri)
return uuid, trigger_uri return uuid, trigger_uri
def _create_app(self):
data = apputils.get_sample_data()
resp = self.client.create_app(data=data)
bdy = json.loads(resp.body)
trigger_uri = bdy['trigger_uri']
return trigger_uri
def test_trigger_post(self): def test_trigger_post(self):
assembly_uuid, plan_uuid, trigger_uri = self._create_assembly() trigger_uri = self._create_app()
# Using requests instead of self.client to test unauthenticated request # Using requests instead of self.client to test unauthenticated request
status_url = 'https://api.github.com/repos/u/r/statuses/{sha}' status_url = 'https://api.github.com/repos/u/r/statuses/{sha}'
body_dict = {'sender': {'url': 'https://api.github.com'}, body_dict = {'sender': {'url': 'https://api.github.com'},
@ -64,8 +73,6 @@ class TestTriggerController(base.TestCase):
resp = requests.post(trigger_uri, data=body) resp = requests.post(trigger_uri, data=body)
self.assertEqual(resp.status_code, 202) self.assertEqual(resp.status_code, 202)
self._delete_assembly(assembly_uuid, plan_uuid)
def test_trigger_post_with_empty_body(self): def test_trigger_post_with_empty_body(self):
assembly_uuid, plan_uuid, trigger_uri = self._create_assembly() assembly_uuid, plan_uuid, trigger_uri = self._create_assembly()
# Using requests instead of self.client to test unauthenticated request # Using requests instead of self.client to test unauthenticated request

View File

@ -19,30 +19,11 @@ from tempest_lib import exceptions as tempest_exceptions
import yaml import yaml
from functionaltests.api import base from functionaltests.api import base
from functionaltests.api.common import apputils
class TestAppController(base.TestCase): class TestAppController(base.TestCase):
def _get_sample_data(self):
data = dict()
data["name"] = "test_app_1"
data["description"] = "descp"
data["languagepack"] = "python"
data["trigger_actions"] = ["test", "build", "deploy"]
data["ports"] = [80]
source = {}
source['repository'] = "https://github.com"
source['revision'] = "master"
data["source"] = source
workflow = {}
workflow["test_cmd"] = "./unit_tests.sh"
workflow["run_cmd"] = "python app.py"
data["workflow_config"] = workflow
return data
def _assert_app_data(self, actual, expected): def _assert_app_data(self, actual, expected):
self.assertEqual(actual["name"], expected["name"]) self.assertEqual(actual["name"], expected["name"])
self.assertEqual(actual["description"], expected["description"]) self.assertEqual(actual["description"], expected["description"])
@ -71,13 +52,13 @@ class TestAppController(base.TestCase):
self.client.delete_created_apps() self.client.delete_created_apps()
def test_app_create(self): def test_app_create(self):
data = self._get_sample_data() data = apputils.get_sample_data()
resp = self.client.create_app(data=data) resp = self.client.create_app(data=data)
self.assertEqual(resp.status, 201) self.assertEqual(resp.status, 201)
def test_app_create_bad_port_data(self): def test_app_create_bad_port_data(self):
try: try:
bad_data = self._get_sample_data() bad_data = apputils.get_sample_data()
bad_data["ports"][0] = -1 bad_data["ports"][0] = -1
self.client.create_plan(data=bad_data) self.client.create_plan(data=bad_data)
except tempest_exceptions.BadRequest: except tempest_exceptions.BadRequest:
@ -89,7 +70,7 @@ class TestAppController(base.TestCase):
headers={'content-type': 'application/json'}) headers={'content-type': 'application/json'})
def test_app_patch(self): def test_app_patch(self):
data = self._get_sample_data() data = apputils.get_sample_data()
create_resp = self.client.create_app(data=data) create_resp = self.client.create_app(data=data)
self.assertEqual(create_resp.status, 201) self.assertEqual(create_resp.status, 201)
@ -116,7 +97,7 @@ class TestAppController(base.TestCase):
self.assertEqual(app_body["source"]["repository"], "newrepo") self.assertEqual(app_body["source"]["repository"], "newrepo")
def test_app_get(self): def test_app_get(self):
data = self._get_sample_data() data = apputils.get_sample_data()
create_resp = self.client.create_app(data=data) create_resp = self.client.create_app(data=data)
self.assertEqual(create_resp.status, 201) self.assertEqual(create_resp.status, 201)
id = create_resp.id id = create_resp.id
@ -129,7 +110,7 @@ class TestAppController(base.TestCase):
self._assert_app_data(yaml_data, data) self._assert_app_data(yaml_data, data)
def test_apps_get_all(self): def test_apps_get_all(self):
data = self._get_sample_data() data = apputils.get_sample_data()
create_resp = self.client.create_app(data) create_resp = self.client.create_app(data)
self.assertEqual(create_resp.status, 201) self.assertEqual(create_resp.status, 201)
resp, body = self.client.get( resp, body = self.client.get(
@ -141,7 +122,7 @@ class TestAppController(base.TestCase):
self.assertEqual(filtered[0]['id'], id) self.assertEqual(filtered[0]['id'], id)
def test_app_delete(self): def test_app_delete(self):
data = self._get_sample_data() data = apputils.get_sample_data()
create_resp = self.client.create_app(data) create_resp = self.client.create_app(data)
self.assertEqual(create_resp.status, 201) self.assertEqual(create_resp.status, 201)
id = create_resp.id id = create_resp.id

View File

@ -106,7 +106,6 @@ class AppsController(rest.RestController):
new_app = handler.create(app_data) new_app = handler.create(app_data)
created_app = app.App.from_db_model(new_app, pecan.request.host_url) created_app = app.App.from_db_model(new_app, pecan.request.host_url)
return created_app return created_app
@exception.wrap_wsme_pecan_controller_exception @exception.wrap_wsme_pecan_controller_exception

View File

@ -47,10 +47,12 @@ class App(api_types.Base):
workflow_config = {wtypes.text: wtypes.text} workflow_config = {wtypes.text: wtypes.text}
trigger_uuid = wtypes.text trigger_uuid = wtypes.text
trigger_actions = [wtypes.text] trigger_actions = [wtypes.text]
trigger_uri = wtypes.text
trust_id = wtypes.text trust_id = wtypes.text
trust_user = wtypes.text trust_user = wtypes.text
app_url = wtypes.text app_url = wtypes.text
status = wtypes.text status = wtypes.text
repo_token = wtypes.text
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(App, self).__init__(*args, **kwargs) super(App, self).__init__(*args, **kwargs)
@ -67,6 +69,8 @@ class App(api_types.Base):
json = m.as_dict() json = m.as_dict()
json['type'] = m.__tablename__ json['type'] = m.__tablename__
json['uri'] = '%s/v1/apps/%s' % (host_url, m.id) json['uri'] = '%s/v1/apps/%s' % (host_url, m.id)
json['trigger_uri'] = ('%s/v1/triggers/%s' %
(host_url, m.trigger_uuid))
return cls(**(json)) return cls(**(json))
def as_dict(self, db_model): def as_dict(self, db_model):

View File

@ -18,8 +18,7 @@ from oslo_config import cfg
import pecan import pecan
from pecan import rest from pecan import rest
from solum.api.handlers import pipeline_handler from solum.api.handlers import app_handler
from solum.api.handlers import plan_handler
from solum.common import exception from solum.common import exception
from solum.openstack.common import log as logging from solum.openstack.common import log as logging
@ -47,6 +46,7 @@ class TriggerController(rest.RestController):
status_url = None status_url = None
collab_url = None collab_url = None
workflow = None workflow = None
try: try:
query = query_dict(pecan.request.query_string) query = query_dict(pecan.request.query_string)
if 'workflow' in query: if 'workflow' in query:
@ -96,11 +96,11 @@ class TriggerController(rest.RestController):
raise exception.BadRequest(reason=info_msg) raise exception.BadRequest(reason=info_msg)
try: try:
handler = plan_handler.PlanHandler(None) handler = app_handler.AppHandler(None)
handler.trigger_workflow(trigger_id, commit_sha, status_url, handler.trigger_workflow(trigger_id, commit_sha, status_url,
collab_url, workflow=workflow) collab_url, workflow=workflow)
except exception.ResourceNotFound: except exception.ResourceNotFound as e:
handler = pipeline_handler.PipelineHandler(None) LOG.error("Incorrect trigger url.")
handler.trigger_workflow(trigger_id) raise e
pecan.response.status = 202 pecan.response.status = 202

View File

@ -78,7 +78,9 @@ class WorkflowsController(rest.RestController):
data.source = app_model.source data.source = app_model.source
wf_data = data.as_dict(workflow.Workflow) wf_data = data.as_dict(workflow.Workflow)
return workflow.Workflow.from_db_model(handler.create(wf_data), return workflow.Workflow.from_db_model(handler.create(wf_data,
commit_sha='',
status_url=''),
pecan.request.host_url) pecan.request.host_url)
@exception.wrap_pecan_controller_exception @exception.wrap_pecan_controller_exception

View File

@ -15,6 +15,9 @@
import uuid import uuid
from solum.api.handlers import handler from solum.api.handlers import handler
from solum.api.handlers import workflow_handler
from solum.common import exception
from solum.common import keystone_utils
from solum import objects from solum import objects
from solum.openstack.common import log as logging from solum.openstack.common import log as logging
@ -62,6 +65,10 @@ class AppHandler(handler.Handler):
db_obj.project_id = self.context.tenant db_obj.project_id = self.context.tenant
db_obj.deleted = False db_obj.deleted = False
# create a delegation trust_id\token, if required
db_obj.trust_id = keystone_utils.create_delegation_token(self.context)
db_obj.trust_user = self.context.user_name
db_obj.name = data.get('name') db_obj.name = data.get('name')
db_obj.description = data.get('description') db_obj.description = data.get('description')
db_obj.languagepack = data.get('languagepack') db_obj.languagepack = data.get('languagepack')
@ -69,14 +76,53 @@ class AppHandler(handler.Handler):
db_obj.ports = data.get('ports') db_obj.ports = data.get('ports')
db_obj.source = data.get('source') db_obj.source = data.get('source')
db_obj.workflow_config = data.get('workflow_config') db_obj.workflow_config = data.get('workflow_config')
db_obj.trigger_uuid = data.get('trigger_uuid') db_obj.trigger_uuid = str(uuid.uuid4())
db_obj.trigger_actions = data.get('trigger_actions') db_obj.trigger_actions = data.get('trigger_actions')
db_obj.trust_id = data.get('trust_id')
db_obj.trust_user = data.get('trust_user')
db_obj.create(self.context) db_obj.create(self.context)
return db_obj return db_obj
def trigger_workflow(self, trigger_id, commit_sha='',
status_url=None, collab_url=None, workflow=None):
"""Get trigger by trigger id and start git workflow associated."""
# Note: self.context will be None at this point as this is a
# non-authenticated request.
app_obj = objects.registry.App.get_by_trigger_id(None, trigger_id)
# get the trust context and authenticate it.
try:
self.context = keystone_utils.create_delegation_context(
app_obj, self.context)
self.context.tenant = app_obj.project_id
self.context.user = app_obj.user_id
self.context.user_name = app_obj.trust_user
except exception.AuthorizationFailure as auth_ex:
LOG.warn(auth_ex)
return
# TODO(devkulkarni): Call repo_utils.verify_artifact
# as we are calling it in the plan_handler to verify
# the collaborator
self._build_artifact(app_obj, commit_sha=commit_sha,
status_url=status_url, wf=workflow)
def _build_artifact(self, app, commit_sha='',
status_url=None, wf=None):
if wf is None:
wf = ['unittest', 'build', 'deploy']
wfhand = workflow_handler.WorkflowHandler(self.context)
wfdata = {
'app_id': app.id,
'name': "%s" % app.name,
'description': '',
'source': app.source,
'config': app.workflow_config,
'actions': wf
}
wfhand.create(wfdata, commit_sha=commit_sha, status_url=status_url)
def get_all(self): def get_all(self):
"""Return all apps.""" """Return all apps."""
all_apps = objects.registry.AppList.get_all(self.context) all_apps = objects.registry.AppList.get_all(self.context)

View File

@ -64,7 +64,7 @@ class WorkflowHandler(handler.Handler):
db_obj = objects.registry.Workflow.get_by_uuid(self.context, id) db_obj = objects.registry.Workflow.get_by_uuid(self.context, id)
db_obj.destroy(self.context) db_obj.destroy(self.context)
def create(self, data): def create(self, data, commit_sha, status_url):
"""Create a new workflow.""" """Create a new workflow."""
db_obj = objects.registry.Workflow() db_obj = objects.registry.Workflow()
db_obj.id = str(uuid.uuid4()) db_obj.id = str(uuid.uuid4())

View File

@ -18,19 +18,16 @@ import mock
from oslo_config import cfg from oslo_config import cfg
from solum.api.controllers.v1 import trigger from solum.api.controllers.v1 import trigger
from solum.common import exception
from solum.tests import base from solum.tests import base
from solum.tests import fakes from solum.tests import fakes
@mock.patch('pecan.request', new_callable=fakes.FakePecanRequest) @mock.patch('pecan.request', new_callable=fakes.FakePecanRequest)
@mock.patch('pecan.response', new_callable=fakes.FakePecanResponse) @mock.patch('pecan.response', new_callable=fakes.FakePecanResponse)
@mock.patch('solum.api.controllers.v1.trigger.plan_handler' @mock.patch('solum.api.controllers.v1.trigger.app_handler'
'.PlanHandler') '.AppHandler')
@mock.patch('solum.api.controllers.v1.trigger.pipeline_handler'
'.PipelineHandler')
class TestTriggerController(base.BaseTestCase): class TestTriggerController(base.BaseTestCase):
def test_trigger_post_with_empty_body(self, pipe_mock, assem_mock, def test_trigger_post_with_empty_body(self, assem_mock,
resp_mock, request_mock): resp_mock, request_mock):
obj = trigger.TriggerController() obj = trigger.TriggerController()
obj.post('test_id') obj.post('test_id')
@ -38,7 +35,7 @@ class TestTriggerController(base.BaseTestCase):
tw = assem_mock.return_value.trigger_workflow tw = assem_mock.return_value.trigger_workflow
assert not tw.called assert not tw.called
def test_trigger_post_on_github_webhook(self, pipe_mock, assem_mock, def test_trigger_post_on_github_webhook(self, assem_mock,
resp_mock, request_mock): resp_mock, request_mock):
status_url = 'https://api.github.com/repos/u/r/statuses/{sha}' status_url = 'https://api.github.com/repos/u/r/statuses/{sha}'
body_dict = {'sender': {'url': 'https://api.github.com'}, body_dict = {'sender': {'url': 'https://api.github.com'},
@ -53,7 +50,7 @@ class TestTriggerController(base.BaseTestCase):
tw.assert_called_once_with('test_id', 'asdf', expected_st_url, None, tw.assert_called_once_with('test_id', 'asdf', expected_st_url, None,
workflow=None) workflow=None)
def test_trigger_post_on_github_comment_webhook(self, pipe_mock, def test_trigger_post_on_github_comment_webhook(self,
assem_mock, resp_mock, assem_mock, resp_mock,
request_mock): request_mock):
cfg.CONF.api.rebuild_phrase = "solum retry tests" cfg.CONF.api.rebuild_phrase = "solum retry tests"
@ -78,7 +75,6 @@ class TestTriggerController(base.BaseTestCase):
@mock.patch('httplib2.Http.request') @mock.patch('httplib2.Http.request')
def test_trigger_post_on_mismatch_comment_pub_repo(self, http_mock, def test_trigger_post_on_mismatch_comment_pub_repo(self, http_mock,
pipe_mock,
assem_mock, resp_mock, assem_mock, resp_mock,
request_mock): request_mock):
cfg.CONF.api.rebuild_phrase = "solum retry tests" cfg.CONF.api.rebuild_phrase = "solum retry tests"
@ -102,7 +98,6 @@ class TestTriggerController(base.BaseTestCase):
@mock.patch('httplib2.Http.request') @mock.patch('httplib2.Http.request')
def test_trigger_post_on_valid_comment_pub_repo(self, http_mock, def test_trigger_post_on_valid_comment_pub_repo(self, http_mock,
pipe_mock,
assem_mock, resp_mock, assem_mock, resp_mock,
request_mock): request_mock):
cfg.CONF.api.rebuild_phrase = "solum retry tests" cfg.CONF.api.rebuild_phrase = "solum retry tests"
@ -127,7 +122,7 @@ class TestTriggerController(base.BaseTestCase):
tw.assert_called_once_with('test_id', 'asdf', expected_st_url, tw.assert_called_once_with('test_id', 'asdf', expected_st_url,
expected_clb_url, workflow=None) expected_clb_url, workflow=None)
def test_trigger_post_on_comment_missing_login(self, pipe_mock, def test_trigger_post_on_comment_missing_login(self,
assem_mock, resp_mock, assem_mock, resp_mock,
request_mock): request_mock):
cfg.CONF.api.rebuild_phrase = "solum retry tests" cfg.CONF.api.rebuild_phrase = "solum retry tests"
@ -148,7 +143,7 @@ class TestTriggerController(base.BaseTestCase):
tw = assem_mock.return_value.trigger_workflow tw = assem_mock.return_value.trigger_workflow
assert not tw.called assert not tw.called
def test_trigger_post_on_wrong_github_webhook(self, pipe_mock, assem_mock, def test_trigger_post_on_wrong_github_webhook(self, assem_mock,
resp_mock, request_mock): resp_mock, request_mock):
status_url = 'https://api.github.com/repos/u/r/statuses/{sha}' status_url = 'https://api.github.com/repos/u/r/statuses/{sha}'
body_dict = {'sender': {'url': 'https://api.github.com'}, body_dict = {'sender': {'url': 'https://api.github.com'},
@ -161,7 +156,7 @@ class TestTriggerController(base.BaseTestCase):
tw = assem_mock.return_value.trigger_workflow tw = assem_mock.return_value.trigger_workflow
assert not tw.called assert not tw.called
def test_trigger_post_on_unknown_git_webhook(self, pipe_mock, assem_mock, def test_trigger_post_on_unknown_git_webhook(self, assem_mock,
resp_mock, request_mock): resp_mock, request_mock):
body_dict = {"pull_request": {"head": {"sha": "asdf"}}} body_dict = {"pull_request": {"head": {"sha": "asdf"}}}
request_mock.body = json.dumps(body_dict) request_mock.body = json.dumps(body_dict)
@ -171,7 +166,7 @@ class TestTriggerController(base.BaseTestCase):
tw = assem_mock.return_value.trigger_workflow tw = assem_mock.return_value.trigger_workflow
assert not tw.called assert not tw.called
def test_trigger_post_on_non_github_webhook(self, pipe_mock, assem_mock, def test_trigger_post_on_non_github_webhook(self, assem_mock,
resp_mock, request_mock): resp_mock, request_mock):
body_dict = {"sender": {"url": "https://non-github.com"}, body_dict = {"sender": {"url": "https://non-github.com"},
"pull_request": {"head": {"sha": "asdf"}}} "pull_request": {"head": {"sha": "asdf"}}}
@ -182,7 +177,7 @@ class TestTriggerController(base.BaseTestCase):
tw = assem_mock.return_value.trigger_workflow tw = assem_mock.return_value.trigger_workflow
assert not tw.called assert not tw.called
def test_trigger_post_on_github_ping_webhook(self, pipe_mock, assem_mock, def test_trigger_post_on_github_ping_webhook(self, assem_mock,
resp_mock, request_mock): resp_mock, request_mock):
body_dict = {"sender": {"url": "https://api.github.com"}, body_dict = {"sender": {"url": "https://api.github.com"},
"zen": "Keep it logically awesome."} "zen": "Keep it logically awesome."}
@ -191,41 +186,4 @@ class TestTriggerController(base.BaseTestCase):
obj.post('test_id') obj.post('test_id')
self.assertEqual(501, resp_mock.status) self.assertEqual(501, resp_mock.status)
tw = assem_mock.return_value.trigger_workflow tw = assem_mock.return_value.trigger_workflow
assert not tw.called assert not tw.called
def test_trigger_post_pipeline(self, pipe_mock, assem_mock,
resp_mock, request_mock):
status_url = 'https://api.github.com/repos/u/r/statuses/{sha}'
body_dict = {'sender': {'url': 'https://api.github.com'},
'pull_request': {'head': {'sha': 'asdf'}},
'repository': {'statuses_url': status_url}}
request_mock.body = json.dumps(body_dict)
obj = trigger.TriggerController()
assem_mock.return_value.trigger_workflow.side_effect = (
exception.ResourceNotFound(name='trigger', id='test_id'))
obj.post('test_id')
self.assertEqual(202, resp_mock.status)
tw = pipe_mock.return_value.trigger_workflow
tw.assert_called_once_with('test_id')
def test_trigger_post_none(self, pipe_mock, assem_mock,
resp_mock, request_mock):
status_url = 'https://api.github.com/repos/u/r/statuses/{sha}'
body_dict = {'sender': {'url': 'https://api.github.com'},
'pull_request': {'head': {'sha': 'asdf'}},
'repository': {'statuses_url': status_url}}
request_mock.body = json.dumps(body_dict)
expected_st_url = 'https://api.github.com/repos/u/r/statuses/asdf'
obj = trigger.TriggerController()
assem_mock.return_value.trigger_workflow.side_effect = (
exception.ResourceNotFound(name='trigger', id='test_id'))
pipe_mock.return_value.trigger_workflow.side_effect = (
exception.ResourceNotFound(name='trigger', id='test_id'))
obj.post('test_id')
self.assertEqual(404, resp_mock.status)
tw = assem_mock.return_value.trigger_workflow
tw.assert_called_once_with('test_id', 'asdf', expected_st_url, None,
workflow=None)
tw = pipe_mock.return_value.trigger_workflow
tw.assert_called_once_with('test_id')

View File

@ -51,7 +51,8 @@ class TestAppHandler(base.BaseTestCase):
get_by_uuid.assert_called_once_with(self.ctx, 'test_id') get_by_uuid.assert_called_once_with(self.ctx, 'test_id')
self.assertTrue(db_obj.destroy.called) self.assertTrue(db_obj.destroy.called)
def test_app_create(self, mock_registry): @mock.patch('solum.common.clients.OpenStackClients.keystone')
def test_app_create(self, mock_kc, mock_registry):
data = {'name': 'fakeapp', data = {'name': 'fakeapp',
'description': 'fake app for testing'} 'description': 'fake app for testing'}
db_obj = fakes.FakeApp() db_obj = fakes.FakeApp()

View File

@ -91,8 +91,7 @@ class TestWorkflowHandler(base.BaseTestCase):
handler = workflow_handler.WorkflowHandler(self.ctx) handler = workflow_handler.WorkflowHandler(self.ctx)
res = handler.create(workflow_data) res = handler.create(workflow_data, commit_sha='', status_url='')
self.assertEqual(wf_obj, res) self.assertEqual(wf_obj, res)
git_info = { git_info = {
'source_url': app_obj.source['repository'], 'source_url': app_obj.source['repository'],

View File

@ -46,14 +46,14 @@ def _get_solum_client():
def main(args): def main(args):
client = _get_solum_client() client = _get_solum_client()
plan = client.plans.find(name_or_id=args.app) app = client.apps.find(name_or_id=args.app)
status_url = 'https://api.github.com/repos/{repo}/statuses/{sha}'.format( status_url = 'https://api.github.com/repos/{repo}/statuses/{sha}'.format(
repo=args.repo, sha=args.sha) repo=args.repo, sha=args.sha)
body_dict = {'sender': {'url': 'https://api.github.com'}, body_dict = {'sender': {'url': 'https://api.github.com'},
'pull_request': {'head': {'sha': args.sha}}, 'pull_request': {'head': {'sha': args.sha}},
'repository': {'statuses_url': status_url}} 'repository': {'statuses_url': status_url}}
body = json.dumps(body_dict) body = json.dumps(body_dict)
trigger_uri = plan.trigger_uri trigger_uri = app.trigger_uri
if args.workflow: if args.workflow:
trigger_uri = "%s?workflow=%s" % (trigger_uri, args.workflow) trigger_uri = "%s?workflow=%s" % (trigger_uri, args.workflow)
resp = requests.post(trigger_uri, data=body) resp = requests.post(trigger_uri, data=body)