shipyard/src/bin/shipyard_airflow/shipyard_airflow/control/configdocs/configdocs_api.py

232 lines
9.3 KiB
Python

# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Resources representing the configdocs API for shipyard
"""
import falcon
from oslo_config import cfg
from shipyard_airflow import policy
from shipyard_airflow.control.api_lock import (api_lock, ApiLockType)
from shipyard_airflow.control.base import BaseResource
from shipyard_airflow.control.helpers import configdocs_helper
from shipyard_airflow.control.helpers.configdocs_helper import (
ConfigdocsHelper)
from shipyard_airflow.errors import ApiError
CONF = cfg.CONF
VERSION_VALUES = ['buffer',
'committed',
'last_site_action',
'successful_site_action']
class ConfigDocsStatusResource(BaseResource):
"""
Configdocs Status handles the retrieval of the configuration documents'
statuses
"""
@policy.ApiEnforcer(policy.GET_CONFIGDOCS_STATUS)
def on_get(self, req, resp):
"""Returns a list of the configdocs and their statuses"""
versions = req.params.get('versions') or None
helper = ConfigdocsHelper(req.context)
resp.body = self.to_json(helper.get_configdocs_status(versions))
resp.status = falcon.HTTP_200
class ConfigDocsResource(BaseResource):
"""
Configdocs handles the creation and retrieval of configuration
documents into Shipyard.
"""
@policy.ApiEnforcer(policy.CREATE_CONFIGDOCS)
@api_lock(ApiLockType.CONFIGDOCS_UPDATE)
def on_post(self, req, resp, collection_id):
"""
Ingests a collection of documents
"""
content_length = req.content_length or 0
if (content_length == 0):
raise ApiError(
title=('Content-Length is a required header'),
description='Content Length is 0 or not specified',
status=falcon.HTTP_400,
error_list=[{
'message': (
'The Content-Length specified is 0 or not set. Check '
'that a valid payload is included with this request '
'and that your client is properly including a '
'Content-Length header. Note that a newline character '
'in a prior header can trigger subsequent headers to '
'be ignored and trigger this failure.')
}],
retry=False, )
document_data = req.stream.read(content_length)
buffer_mode = req.get_param('buffermode')
helper = ConfigdocsHelper(req.context)
validations = self.post_collection(
helper=helper,
collection_id=collection_id,
document_data=document_data,
buffer_mode_param=buffer_mode)
resp.status = falcon.HTTP_201
if validations and validations['status'] == 'Success':
validations['code'] = resp.status
resp.location = '/api/v1.0/configdocs/{}'.format(collection_id)
resp.body = self.to_json(validations)
@policy.ApiEnforcer(policy.GET_CONFIGDOCS)
def on_get(self, req, resp, collection_id):
"""
Returns a collection of documents
"""
version = (req.params.get('version') or 'buffer')
cleartext_secrets = req.get_param_as_bool('cleartext-secrets') or False
self._validate_version_parameter(version)
helper = ConfigdocsHelper(req.context)
# Not reformatting to JSON or YAML since just passing through
resp.body = self.get_collection(
helper=helper, collection_id=collection_id, version=version,
cleartext_secrets=cleartext_secrets)
resp.append_header('Content-Type', 'application/x-yaml')
resp.status = falcon.HTTP_200
def _validate_version_parameter(self, version):
# performs validation of version parameter
if version.lower() not in VERSION_VALUES:
raise ApiError(
title='Invalid version query parameter specified',
description=(
'version must be {}'.format(', '.join(VERSION_VALUES))),
status=falcon.HTTP_400,
retry=False, )
def get_collection(self, helper, collection_id, version='buffer',
cleartext_secrets=False):
"""
Attempts to retrieve the specified collection of documents
either from the buffer, committed version, last site action
or successful site action, as specified
"""
return helper.get_collection_docs(version, collection_id,
cleartext_secrets)
def post_collection(self,
helper,
collection_id,
document_data,
buffer_mode_param=None):
"""
Ingest the collection after checking preconditions
"""
buffer_mode = ConfigdocsHelper.get_buffer_mode(buffer_mode_param)
if helper.is_buffer_valid_for_bucket(collection_id, buffer_mode):
buffer_revision = helper.add_collection(collection_id,
document_data)
if helper.is_collection_in_buffer(collection_id):
return helper.get_deckhand_validation_status(buffer_revision)
else:
raise ApiError(
title=('Collection {} not added to Shipyard '
'buffer'.format(collection_id)),
description='Collection empty or resulted in no revision',
status=falcon.HTTP_400,
error_list=[{
'message': (
'Empty collections are not supported. After '
'processing, the collection {} added no new '
'revision, and has been rejected as invalid '
'input'.format(collection_id))
}],
retry=False,
)
else:
raise ApiError(
title='Invalid collection specified for buffer',
description='Buffermode : {}'.format(buffer_mode.value),
status=falcon.HTTP_409,
error_list=[{
'message': ('Buffer is either not empty or the '
'collection already exists in buffer. '
'Setting a different buffermode may '
'provide the desired functionality')
}],
retry=False,
)
class CommitConfigDocsResource(BaseResource):
"""
Commits the buffered configdocs, if the validations pass (or are
overridden (force = true))
Returns the list of validations.
"""
unable_to_commmit = 'Unable to commit configuration documents'
@policy.ApiEnforcer(policy.COMMIT_CONFIGDOCS)
@api_lock(ApiLockType.CONFIGDOCS_UPDATE)
def on_post(self, req, resp):
"""
Get validations from all Airship components
Functionality does not exist yet
"""
# force and dryrun query parameter is False unless explicitly true
force = req.get_param_as_bool(name='force') or False
dryrun = req.get_param_as_bool(name='dryrun') or False
helper = ConfigdocsHelper(req.context)
validations = self.commit_configdocs(helper, force, dryrun)
resp.body = self.to_json(validations)
resp.status = validations.get('code', falcon.HTTP_200)
def commit_configdocs(self, helper, force, dryrun):
"""
Attempts to commit the configdocs
"""
if helper.is_buffer_empty():
raise ApiError(
title=CommitConfigDocsResource.unable_to_commmit,
description='There are no documents in the buffer to commit',
status=falcon.HTTP_409,
retry=True)
validations = helper.get_validations_for_revision(
helper.get_revision_id(configdocs_helper.BUFFER)
)
if dryrun:
validations['code'] = falcon.HTTP_200
if 'message' in validations:
validations['message'] = (
validations['message'] + ' DRYRUN')
else:
validations['message'] = 'DRYRUN'
else:
if force or validations.get('status') == 'Success':
helper.tag_buffer(configdocs_helper.COMMITTED)
if force and validations.get('status') == 'Failure':
# override the status in the response
validations['code'] = falcon.HTTP_200
if 'message' in validations:
validations['message'] = (
validations['message'] + ' FORCED SUCCESS')
else:
validations['message'] = 'FORCED SUCCESS'
return validations