Skip to content

Commit

Permalink
New API method get_pods_for_build() (fixes #238)
Browse files Browse the repository at this point in the history
  • Loading branch information
twaugh committed Sep 3, 2015
1 parent 2cefc73 commit da5169b
Show file tree
Hide file tree
Showing 10 changed files with 447 additions and 19 deletions.
16 changes: 15 additions & 1 deletion osbs/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .constants import SIMPLE_BUILD_TYPE, PROD_WITHOUT_KOJI_BUILD_TYPE, PROD_WITH_SECRET_BUILD_TYPE
from osbs.build.build_request import BuildManager
from osbs.build.build_response import BuildResponse
from osbs.build.pod_response import PodResponse
from osbs.constants import DEFAULT_NAMESPACE, PROD_BUILD_TYPE
from osbs.core import Openshift
from osbs.exceptions import OsbsException, OsbsValidationException
Expand Down Expand Up @@ -51,7 +52,7 @@ def catch_exceptions(*args, **kwargs):
class OSBS(object):
"""
Note: all API methods return osbs.http.Response object. This is, due to historical
reasons, untrue for list_builds and get_user, which return list of BuildResult objects
reasons, untrue for list_builds and get_user, which return list of BuildResponse objects
and dict respectively.
"""
@osbsapi
Expand All @@ -62,6 +63,7 @@ def __init__(self, openshift_configuration, build_configuration):
self.os = Openshift(openshift_api_url=self.os_conf.get_openshift_api_uri(),
openshift_api_version=self.os_conf.get_openshift_api_version(),
openshift_oauth_url=self.os_conf.get_openshift_oauth_api_uri(),
k8s_api_url=self.os_conf.get_k8s_api_uri(),
verbose=self.os_conf.get_verbosity(),
username=self.os_conf.get_username(),
password=self.os_conf.get_password(),
Expand Down Expand Up @@ -103,6 +105,18 @@ def cancel_build(self, build_id, namespace=DEFAULT_NAMESPACE):
build_response = BuildResponse(response)
return build_response

@osbsapi
def get_pods_for_build(self, build_id, namespace=DEFAULT_NAMESPACE):
"""
:return: list, PodResponse objects for pods relating to the build
"""
response = self.os.get_pods_for_build(build_id, namespace=namespace)
serialized_response = response.json()
pod_list = []
for pod in serialized_response["items"]:
pod_list.append(PodResponse(pod))
return pod_list

@osbsapi
def get_build_request(self, build_type=None):
"""
Expand Down
1 change: 1 addition & 0 deletions osbs/build/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
from __future__ import absolute_import

from .build_response import BuildResponse
from .pod_response import PodResponse
53 changes: 53 additions & 0 deletions osbs/build/pod_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
Copyright (c) 2015 Red Hat, Inc
All rights reserved.
This software may be modified and distributed under the terms
of the BSD license. See the LICENSE file for details.
"""
from __future__ import print_function, absolute_import, unicode_literals

import json
import logging

from osbs.utils import graceful_chain_get


logger = logging.getLogger(__name__)


class PodResponse(object):
"""
Wrapper for JSON describing build pod
"""

def __init__(self, pod):
"""
:param request: http.Request
"""
self._json = pod

@property
def json(self):
return self._json

def get_container_image_ids(self):
"""
Find the image IDs the containers use.
:return: dict, container name to ID
"""

statuses = graceful_chain_get(self.json, "status", "containerStatuses")
if statuses is None:
return {}

def remove_prefix(image_id, prefix):
if image_id.startswith(prefix):
return image_id[len(prefix):]

return image_id

return dict([(status['name'], remove_prefix(status['imageID'],
'docker://'))
for status in statuses])
23 changes: 23 additions & 0 deletions osbs/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,23 @@ def cmd_get_user(args, osbs):
print("Name: \"%s\"\nFull Name: \"%s\"" % (name, full_name))


def cmd_get_build_image_id(args, osbs):
pods = osbs.get_pods_for_build(args.BUILD_ID[0], namespace=args.namespace)
if len(pods) == 0:
return
assert len(pods) == 1
pod = pods[0]
if args.output == 'json':
json_output = pod.get_container_image_ids()
print_json_nicely(json_output)
elif args.output == 'text':
format_str = "{name:14} {image:64}"
print(format_str.format(name='NAME', image='IMAGE'))
image_ids = pod.get_container_image_ids()
for name, image_id in image_ids.items():
print(format_str.format(name=name, image=image_id))


def str_on_2_unicode_on_3(s):
"""
argparse is way too awesome when doing repr() on choices when printing usage
Expand Down Expand Up @@ -320,6 +337,12 @@ def cli():
help="storage limit")
build_parser.set_defaults(func=cmd_build)

get_build_image_id = subparsers.add_parser(str_on_2_unicode_on_3('get-build-image-id'),
help='get build container image ID',
description='get build container images for a build in a namespace')
get_build_image_id.add_argument("BUILD_ID", help="build ID", nargs=1)
get_build_image_id.set_defaults(func=cmd_get_build_image_id)

parser.add_argument("--openshift-uri", action='store', metavar="URL",
help="openshift URL to remote API")
parser.add_argument("--registry-uri", action='store', metavar="URL",
Expand Down
19 changes: 16 additions & 3 deletions osbs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,28 @@ def get_openshift_api_version():
# This is not configurable.
return "v1"

def _get_api_uri(self, keyword):
base_uri = self.get_openshift_base_uri()
version = self.get_openshift_api_version()
return urljoin(base_uri,
"/{keyword}/{version}/".format(keyword=keyword,
version=version))

def get_k8s_api_uri(self):
"""
https://<host>[:<port>]/api/<API version>/
:return: str
"""
return self._get_api_uri('api')

def get_openshift_api_uri(self):
"""
https://<host>[:<port>]/oapi/<API version>/
:return: str
"""
base_uri = self.get_openshift_base_uri()
version = self.get_openshift_api_version()
return urljoin(base_uri, "/oapi/{version}/".format(version=version))
return self._get_api_uri('oapi')

def get_openshift_oauth_api_uri(self):
"""
Expand Down
13 changes: 13 additions & 0 deletions osbs/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@ def check_response(response):
# TODO: error handling: create function which handles errors in response object
class Openshift(object):
def __init__(self, openshift_api_url, openshift_api_version, openshift_oauth_url,
k8s_api_url=None,
verbose=False, username=None, password=None, use_kerberos=False,
kerberos_keytab=None, kerberos_principal=None, kerberos_ccache=None,
client_cert=None, client_key=None, verify_ssl=True, use_auth=None):
self.os_api_url = openshift_api_url
self.k8s_api_url = k8s_api_url
self._os_api_version = openshift_api_version
self._os_oauth_url = openshift_oauth_url
self.verbose = verbose
Expand All @@ -76,6 +78,11 @@ def __init__(self, openshift_api_url, openshift_api_version, openshift_oauth_url
def os_oauth_url(self):
return self._os_oauth_url

def _build_k8s_url(self, url, **query):
if query:
url += ("?" + urlencode(query))
return urlparse.urljoin(self.k8s_api_url, url)

def _build_url(self, url, **query):
if query:
url += ("?" + urlencode(query))
Expand Down Expand Up @@ -183,6 +190,12 @@ def cancel_build(self, build_id, namespace=DEFAULT_NAMESPACE):
return self._put(url, data=json.dumps(br.json),
headers={"Content-Type": "application/json"})

def get_pods_for_build(self, build_id, namespace=DEFAULT_NAMESPACE):
labelSelector='openshift.io/build.name=%s' % build_id
url = self._build_k8s_url("namespaces/%s/pods/" % namespace,
labelSelector=labelSelector)
return self._get(url)

def get_build_config(self, build_config_id, namespace=DEFAULT_NAMESPACE):
url = self._build_url("namespaces/%s/buildconfigs/%s/" % (namespace, build_config_id))
response = self._get(url)
Expand Down
41 changes: 26 additions & 15 deletions tests/fake_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@

logger = logging.getLogger("osbs.tests")
API_VER = Configuration.get_openshift_api_version()
API_PREFIX = "/oapi/{v}/".format(v=API_VER)
OAPI_PREFIX = "/oapi/{v}/".format(v=API_VER)
API_PREFIX = "/api/{v}/".format(v=API_VER)


class StreamingResponse(object):
Expand Down Expand Up @@ -61,7 +62,7 @@ def __init__(self, version="0.5.4"):
# The files are captured using the command line tool's
# --capture-dir parameter, and edited as needed.
self.DEFINITION = {
API_PREFIX + "namespaces/default/builds/": {
OAPI_PREFIX + "namespaces/default/builds/": {
"get": {
# Contains a list of builds
"file": "builds_list.json",
Expand All @@ -73,8 +74,8 @@ def __init__(self, version="0.5.4"):
},

# Some 'builds' requests are with a trailing slash, some without:
(API_PREFIX + "namespaces/default/builds/%s" % TEST_BUILD,
API_PREFIX + "namespaces/default/builds/%s/" % TEST_BUILD): {
(OAPI_PREFIX + "namespaces/default/builds/%s" % TEST_BUILD,
OAPI_PREFIX + "namespaces/default/builds/%s/" % TEST_BUILD): {
"get": {
# Contains a single build in Completed phase
# named test-build-123
Expand All @@ -85,9 +86,9 @@ def __init__(self, version="0.5.4"):
}
},

(API_PREFIX + "namespaces/default/builds/%s/log/" % TEST_BUILD,
API_PREFIX + "namespaces/default/builds/%s/log/?follow=0" % TEST_BUILD,
API_PREFIX + "namespaces/default/builds/%s/log/?follow=1" % TEST_BUILD): {
(OAPI_PREFIX + "namespaces/default/builds/%s/log/" % TEST_BUILD,
OAPI_PREFIX + "namespaces/default/builds/%s/log/?follow=0" % TEST_BUILD,
OAPI_PREFIX + "namespaces/default/builds/%s/log/?follow=1" % TEST_BUILD): {
"get": {
# Lines of text
"file": "build_test-build-123_logs.txt",
Expand All @@ -103,28 +104,28 @@ def __init__(self, version="0.5.4"):
}
},

API_PREFIX + "users/~/": {
OAPI_PREFIX + "users/~/": {
"get": {
"file": "get_user.json",
}
},

API_PREFIX + "watch/namespaces/default/builds/%s/" % TEST_BUILD: {
OAPI_PREFIX + "watch/namespaces/default/builds/%s/" % TEST_BUILD: {
"get": {
# Single MODIFIED item, with a Build object in
# Completed phase named test-build-123
"file": "watch_build_test-build-123.json",
}
},

API_PREFIX + "namespaces/default/buildconfigs/": {
OAPI_PREFIX + "namespaces/default/buildconfigs/": {
"post": {
# Contains a BuildConfig named test-build-config-123
"file": "created_build_config_test-build-config-123.json",
}
},

API_PREFIX + "namespaces/default/buildconfigs/%s/instantiate" % TEST_BUILD_CONFIG: {
OAPI_PREFIX + "namespaces/default/buildconfigs/%s/instantiate" % TEST_BUILD_CONFIG: {
"post": {
# A Build named test-build-123 instantiated from a
# BuildConfig named test-build-config-123
Expand All @@ -133,16 +134,16 @@ def __init__(self, version="0.5.4"):
},

# use both version with ending slash and without it
(API_PREFIX + "namespaces/default/buildconfigs/%s" % TEST_BUILD_CONFIG,
API_PREFIX + "namespaces/default/buildconfigs/%s/" % TEST_BUILD_CONFIG): {
(OAPI_PREFIX + "namespaces/default/buildconfigs/%s" % TEST_BUILD_CONFIG,
OAPI_PREFIX + "namespaces/default/buildconfigs/%s/" % TEST_BUILD_CONFIG): {
"get": {
"custom_callback": self.buildconfig_not_found,
# Empty file (no response content as the status is 404
"file": "not_found_build-config-component-master.json",
}
},

API_PREFIX + "namespaces/default/builds/?labelSelector=buildconfig%%3D%s" %
OAPI_PREFIX + "namespaces/default/builds/?labelSelector=buildconfig%%3D%s" %
TEST_BUILD_CONFIG: {
"get": {
# Contains a BuildList with Builds labeled with
Expand All @@ -151,6 +152,15 @@ def __init__(self, version="0.5.4"):
"file": "builds_list.json"
}
},

API_PREFIX + "namespaces/default/pods/?labelSelector=openshift.io%%2Fbuild.name%%3D%s" %
TEST_BUILD: {
"get": {
# Contains a list of build pods, just needs not to
# be empty
"file": "pods.json",
},
},
}


Expand Down Expand Up @@ -219,7 +229,8 @@ def put(self, url, *args, **kwargs):

@pytest.fixture(params=["0.5.4", "1.0.4"])
def openshift(request):
os_inst = Openshift(API_PREFIX, API_VER, "/oauth/authorize")
os_inst = Openshift(OAPI_PREFIX, API_VER, "/oauth/authorize",
k8s_api_url=API_PREFIX)
os_inst._con = Connection(request.param)
return os_inst

Expand Down
Loading

0 comments on commit da5169b

Please sign in to comment.