def import_all(self): """Import content.""" content_digest = Config.operation_digest content_uuid = Config.operation_uuid if content_digest or content_uuid: collection = self._storage.search(uuid=content_uuid, digest=content_digest) if len(collection) == 1: resource = next(collection.resources()) digest = resource.digest updates = Migrate.load(Config.get_operation_file()) self._logger.debug('updating: %s ' % resource.category, ':with: uuid: %.16s' % content_uuid if content_uuid else ':with: digest: %.16s' % resource.digest) if len(updates) == 1: resource.migrate(next(updates.resources())) self._storage.update(digest, resource) else: Cause.push(Cause.HTTP_BAD_REQUEST, 'updates for content: %.16s :could not be used' % digest) else: Config.validate_search_context(collection, 'import') else: self._logger.debug('importing content: %s', Config.get_operation_file()) collection = Migrate.load(Config.get_operation_file()) self._storage.import_content(collection)
def on_get(self, request, response): """Search unique resource attributes. Search is made from all content categories by default. Args: request (obj): Falcon Request(). response (obj): Falcon Response(). """ self._logger.debug('run: %s %s', request.method, request.uri) if 'scat' not in request.params: request.params['scat'] = Const.CATEGORIES api = Api(self._category, Api.UNIQUE, request.params) Config.load(api) self._content.run() if not self._content.uniques: Cause.push( Cause.HTTP_NOT_FOUND, 'cannot find unique fields for %s attribute' % self._category) if Cause.is_ok(): response.content_type = ApiResource.MEDIA_JSON_API response.body = Generate.fields(self._category, self._content.uniques, request, response) response.status = Cause.http_status() else: response.content_type = ApiResource.MEDIA_JSON_API response.body = Generate.error(Cause.json_message()) response.status = Cause.http_status() Cause.reset() self._logger.debug('end: %s %s', request.method, request.uri)
def update(self): """Update content.""" collection = self._storage.search( scat=Config.search_cat_kws, sall=Config.search_all_kws, stag=Config.search_tag_kws, sgrp=Config.search_grp_kws, search_filter=Config.search_filter, uuid=Config.operation_uuid, digest=Config.operation_digest, identity=Config.operation_identity, data=Config.content_data ) if len(collection) == 1: stored = next(collection.resources()) digest = stored.digest updates = Config.get_resource(stored) if updates and updates != stored: self._logger.debug('updating stored: %s :with digest: %.16s', self._category, digest) stored.migrate(updates) self.collection = self._storage.update(digest, stored) else: self._logger.debug('content: %s :with digest: %.16s :was not updated', self._category, digest) else: Config.validate_search_context(collection, 'update')
def on_get(self, request, response, sall=None, stag=None, sgrp=None): """Search resources. Args: request (obj): Falcon Request(). response (obj): Falcon Response(). sall (str): Search all ``sall`` path parameter. stag (str): Search tags ``stag`` path parameter. sgrp (str): Search groups ``sgrp`` path parameter. """ self._logger.debug('run: %s %s', request.method, request.uri) if sall: request.params['sall'] = sall if stag: request.params['stag'] = stag if sgrp: request.params['sgrp'] = sgrp api = Api(self._category, Api.SEARCH, request.params) Config.load(api) self._content.run() if not self._content.collection and Config.search_limit != 0: Cause.push(Cause.HTTP_NOT_FOUND, 'cannot find resources') if Cause.is_ok(): response.content_type = ApiResource.MEDIA_JSON_API response.body = Generate.collection(self._content.collection, request, response, pagination=True) response.status = Cause.http_status() else: response.content_type = ApiResource.MEDIA_JSON_API response.body = Generate.error(Cause.json_message()) response.status = Cause.http_status() Cause.reset() self._logger.debug('end: %s %s', request.method, request.uri)
def on_get(self, request, response, identity, field): """Get defined content field based on resource ID. If the given uuid matches to multiple resources or no resources at all, an error is returned. This conflicts against the JSON API v1.0 specifications. See the Snippy documentation for more information. Args: request (obj): Falcon Request(). response (obj): Falcon Response(). identity (str): Partial or full message digest or UUID. field (str): Resource attribute. """ self._logger.debug('run: %s %s', request.method, request.uri) local_params = {'identity': identity, 'fields': field} api = Api(self._category, Api.SEARCH, local_params) Config.load(api) self._content.run() if len(self._content.collection) != 1: Cause.push(Cause.HTTP_NOT_FOUND, 'content identity: %s was not unique and matched to: %d resources' % (identity, len(self._content.collection))) if Cause.is_ok(): response.content_type = ApiResource.MEDIA_JSON_API response.body = Generate.resource(self._content.collection, request, response, identity, field=field, pagination=False) response.status = Cause.http_status() else: response.content_type = ApiResource.MEDIA_JSON_API response.body = Generate.error(Cause.json_message()) response.status = Cause.http_status() Cause.reset() self._logger.debug('end: %s %s', request.method, request.uri)
def on_post(self, request, response, **kwargs): # pylint: disable=unused-argument """Create new resource. Args: request (obj): Falcon Request(). response (obj): Falcon Response(). """ self._logger.debug('run: %s %s', request.method, request.uri) collection = Collection() data = Validate.json_object(request) for resource in data: api = Api(self._category, Api.CREATE, resource) Config.load(api) self._content.run(collection) if Cause.is_ok(): response.content_type = ApiResource.MEDIA_JSON_API response.body = Generate.collection(collection, request, response) response.status = Cause.http_status() else: response.content_type = ApiResource.MEDIA_JSON_API response.body = Generate.error(Cause.json_message()) response.status = Cause.http_status() Cause.reset() self._logger.debug('end: %s %s', request.method, request.uri)
def on_put(self, request, response, identity): """Update resource based on the resource ID. Args: request (obj): Falcon Request(). response (obj): Falcon Response(). identity (str): Partial or full message digest or UUID. """ self._logger.debug('run: %s %s', request.method, request.uri) collection = Validate.json_object(request, identity) for resource in collection: api = Api(self._category, Api.UPDATE, resource) Config.load(api) self._content.run() if Cause.is_ok(): response.content_type = ApiResource.MEDIA_JSON_API response.body = Generate.resource(self._content.collection, request, response, identity) response.status = Cause.http_status() else: response.content_type = ApiResource.MEDIA_JSON_API response.body = Generate.error(Cause.json_message()) response.status = Cause.http_status() Cause.reset() self._logger.debug('end: %s %s', request.method, request.uri)
def test_config_create_006(): """Test that tags can be added inside quotes separated by comma and space after comma.""" content = 'docker rm $(docker ps -a -q)' brief = 'Remove all docker containers' groups = ('docker', ) tags = ('cleanup', 'container', 'docker') links = ( 'https://askubuntu.com/questions/574163/how-to-stop-and-remove-a-docker-container', ) Config.init(None) Config.load( Cli([ 'snippy', 'create', '-c', content, '-b', brief, '-g', 'docker', '-t', 'docker, container, cleanup', '-l', links[0] ])) assert isinstance(Config.content_data, tuple) assert isinstance(Config.content_brief, Const.TEXT_TYPE) assert isinstance(Config.content_groups, tuple) assert isinstance(Config.content_tags, tuple) assert isinstance(Config.content_links, tuple) assert Config.content_data == tuple([content]) assert Config.content_brief == brief assert Config.content_groups == groups assert Config.content_tags == tags assert Config.content_links == links
def test_config_create_013(): """Test that multiple links can be added by separating them with bar.""" content = 'docker rm $(docker ps -a -q)' brief = 'Remove all docker containers' tags = ('cleanup', 'container', 'docker') links = ( 'https://askubuntu.com/questions/574163/how-to-stop-and-remove-a-docker-container', 'https://www.digitalocean.com/community/tutorials/how-to-remove-docker-images-containers-and-volumes' ) Config.init(None) Config.load( Cli([ 'snippy', 'create', '-c', content, '-b', brief, '-t', 'docker, container, cleanup', '-l', '|'.join(links) ])) assert isinstance(Config.content_data, tuple) assert isinstance(Config.content_brief, Const.TEXT_TYPE) assert isinstance(Config.content_tags, tuple) assert isinstance(Config.content_links, tuple) assert Config.content_data == tuple([content]) assert Config.content_brief == brief assert Config.content_tags == tags assert Config.content_links == links
def database_mock(request, mocker): """Mock database for testing.""" from snippy.storage.database import Database as Db Config.init(['snippy', '-q'] + Database.get_cli_params() ) # Prevent unnecessary CLI help output with quiet option. mocker.patch.object(Config, 'storage_file', Database.get_storage(), create=True) mocker.patch.object(Config, 'storage_schema', Database.get_schema(), create=True) database = Db() database.init() def fin(): """Clear the resources at the end.""" database.disconnect() Database.delete_all_contents() Database.delete_storage() request.addfinalizer(fin) return database
def release(self): """Release service resource.""" self.storage.disconnect() Cause.reset() Config.reset() Logger.reset()
def test_config_search_001(): """Test that search can be used with one keyword.""" search_kw = ('docker', ) Config.init(None) Config.load(Cli(['snippy', 'search', '--sall', 'docker'])) assert isinstance(Config.search_all_kws, tuple) assert Config.search_all_kws == search_kw
def test_config_search_004(): """Test that search keywords can be added so that they are separated by spaces before and after the words.""" search_kw = ('cleanup', 'container', 'docker') Config.init(None) Config.load( Cli(['snippy', 'search', '--sall', 'docker, container, cleanup'])) assert isinstance(Config.search_all_kws, tuple) assert Config.search_all_kws == search_kw
def test_config_search_003(): """Test that search keywords can be added inside quotes separated by comma and spaces after comma.""" search_kw = ('cleanup', 'container', 'docker') Config.init(None) Config.load( Cli(['snippy', 'search', '--sall', 'docker, container, cleanup'])) assert isinstance(Config.search_all_kws, tuple) assert Config.search_all_kws == search_kw
def test_config_create_002(): """Test that new snippet can be created without optional arguments.""" content = 'docker rm $(docker ps -a -q)' Config.init(None) Config.load(Cli(['snippy', 'create', '-c', content])) assert isinstance(Config.content_data, tuple) assert isinstance(Config.content_brief, Const.TEXT_TYPE) assert isinstance(Config.content_tags, tuple) assert Config.content_data == tuple([content]) assert not Config.content_brief assert not Config.content_tags
def test_config_create_004(): """Test that new snippet can be created with a single tag.""" content = 'docker rm $(docker ps -a -q)' tags = ('docker', ) Config.init(None) Config.load(Cli(['snippy', 'create', '-c', content, '-t', 'docker'])) assert isinstance(Config.content_data, tuple) assert isinstance(Config.content_brief, Const.TEXT_TYPE) assert isinstance(Config.content_tags, tuple) assert Config.content_data == tuple([content]) assert not Config.content_brief assert Config.content_tags == tags
def test_config_create_003(): """Test that new snippet can be created with brief description but no tags.""" content = 'docker rm $(docker ps -a -q)' brief = 'Remove all docker containers' Config.init(None) Config.load(Cli(['snippy', 'create', '-c', content, '-b', brief])) assert isinstance(Config.content_data, tuple) assert isinstance(Config.content_brief, Const.TEXT_TYPE) assert isinstance(Config.content_tags, tuple) assert Config.content_data == tuple([content]) assert Config.content_brief == brief assert not Config.content_tags
def test_config_search_007(): """Test that search keywords are accepted if they contain special characters.""" search_kw = (u'cleanup_testing', u'container-managemenet', u'docker–testing') Config.init(None) Config.load( Cli([ 'snippy', 'search', '--sall', 'docker–testing, ', 'container-managemenet, ', 'cleanup_testing' ])) assert isinstance(Config.search_all_kws, tuple) assert Config.search_all_kws == search_kw assert len(Config.search_all_kws) == 3
def create(self): """Create new content.""" self._logger.debug('creating new: %s', self._category) collection = Config.get_collection() collection = self._storage.create(collection) self.collection.migrate(collection)
def dump_completion(cls, complete): """Dump shell completion script into a file. Args: complete (str): Name of the shell for completion. """ filename = Config.get_operation_file() path, _ = os.path.split(filename) cls._logger.debug('exporting: %s :completion: %s', Config.complete, filename) if not os.path.exists(path) or not os.access(path, os.W_OK): Cause.push( Cause.HTTP_BAD_REQUEST, 'cannot export: {} :completion file because path is not writable: {}' .format(complete, filename)) return with open(filename, 'w') as outfile: try: outfile.write(Config.completion[Config.complete]) except IOError as error: cls._logger.exception( 'fatal failure when creating {} shell completion file: {}', filename, error) Cause.push( Cause.HTTP_INTERNAL_SERVER_ERROR, 'fatal failure while exporting shell completion {}'.format( filename))
def _get_schema_validator(): """Get schema validator. Returns: obj: Jsonschema draft7 validator. """ schema = Config.server_schema() Draft7Validator.check_schema(schema) resolver = RefResolver(base_uri=Config.server_schema_base_uri(), referrer=schema) validator = Draft7Validator(schema, resolver=resolver, format_checker=None) return validator
def test_config_create_009(): """Test that tags can be added so that they are separated by comma after the words like in '-t docker, container, cleanup'.""" content = 'docker rm $(docker ps -a -q)' tags = ('cleanup', 'container', 'docker') Config.init(None) Config.load( Cli([ 'snippy', 'create', '-c', content, '-t', 'docker,', 'container,', 'cleanup' ])) assert isinstance(Config.content_data, tuple) assert isinstance(Config.content_brief, Const.TEXT_TYPE) assert isinstance(Config.content_tags, tuple) assert Config.content_data == tuple([content]) assert Config.content_tags == tags
def import_all(self): """Import content.""" if Config.defaults: self._logger.debug('importing all default content') collection = Migrate.load( Config.default_content_file(Const.SNIPPET)) collection.migrate( Migrate.load(Config.default_content_file(Const.SOLUTION))) collection.migrate( Migrate.load(Config.default_content_file(Const.REFERENCE))) self._storage.import_content(collection) else: Cause.push( Cause.HTTP_BAD_REQUEST, 'import operation for content category \'all\' is supported only with default content' )
def test_config_create_010(): """Test that tags are accepted if they contain special characters.""" content = 'docker rm $(docker ps -a -q)' tags = (u'cleanup_testing', u'container-managemenet', u'docker–testing') Config.init(None) Config.load( Cli([ 'snippy', 'create', '-c', content, '-t', 'docker–testing, ', 'container-managemenet, ', 'cleanup_testing' ])) assert isinstance(Config.content_data, tuple) assert isinstance(Config.content_brief, Const.TEXT_TYPE) assert isinstance(Config.content_tags, tuple) assert Config.content_data == tuple([content]) assert Config.content_tags == tags assert len(Config.content_tags) == 3
def test_config_create_005(): """Test that tags can be added inside quotes separated by comma and without spaces.""" content = 'docker rm $(docker ps -a -q)' tags = ('cleanup', 'container', 'docker') Config.init(None) Config.load( Cli([ 'snippy', 'create', '-c', content, '-t', 'docker,container,cleanup' ])) assert isinstance(Config.content_data, tuple) assert isinstance(Config.content_brief, Const.TEXT_TYPE) assert isinstance(Config.content_tags, tuple) assert Config.content_data == tuple([content]) assert not Config.content_brief assert Config.content_tags == tags
def test_config_create_011(): """Test that tags are accepted if the tags are elements in a list. This might not be realistic case since user might not be able to reproduce this?""" content = 'docker rm $(docker ps -a -q)' tags = ('cleanup', 'container', 'docker') Config.init(None) Config.load( Cli([ 'snippy', 'create', '-c', content, '-t', 'docker', 'container', 'cleanup' ])) assert isinstance(Config.content_data, tuple) assert isinstance(Config.content_brief, Const.TEXT_TYPE) assert isinstance(Config.content_tags, tuple) assert Config.content_data == tuple([content]) assert Config.content_tags == tags
def dump(cls, collection, filename): """Dump collection into file.""" if not Config.is_supported_file_format(): cls._logger.debug('file format not supported for file %s', filename) return if not collection: Cause.push(Cause.HTTP_NOT_FOUND, 'no content found to be exported') return cls._logger.debug('exporting contents %s', filename) with open(filename, 'w') as outfile: try: dictionary = { 'meta': { 'updated': Config.utcnow(), 'version': __version__, 'homepage': __homepage__ }, 'data': collection.dump_dict(Config.remove_fields) } if Config.is_operation_file_text: outfile.write(collection.dump_text(Config.templates)) elif Config.is_operation_file_json: json.dump(dictionary, outfile) outfile.write(Const.NEWLINE) elif Config.is_operation_file_mkdn: outfile.write(collection.dump_mkdn(Config.templates)) elif Config.is_operation_file_yaml: yaml.safe_dump(dictionary, outfile, default_flow_style=False) else: cls._logger.debug('unknown export file format') except (IOError, TypeError, ValueError, yaml.YAMLError) as error: cls._logger.exception( 'fatal failure to generate formatted export file "%s"', error) Cause.push(Cause.HTTP_INTERNAL_SERVER_ERROR, 'fatal failure while exporting content to file')
def dump_template(cls, category): """Dump content template into file.""" filename = Config.get_operation_file() resource = Collection.get_resource(category, Config.utcnow()) template = resource.get_template(category, Config.template_format, Config.templates) cls._logger.debug('exporting content template %s', filename) with open(filename, 'w') as outfile: try: outfile.write(template) except IOError as error: cls._logger.exception( 'fatal failure in creating %s template file "%s"', category, error) Cause.push( Cause.HTTP_INTERNAL_SERVER_ERROR, 'fatal failure while exporting template {}'.format( filename))
def on_get(self, request, response, scat=None, sall=None, stag=None, sgrp=None): """Search unique groups. By default the search is made from all content categories. Args: request (obj): Falcon Request(). response (obj): Falcon Response(). scat (str): Search categories ``scat`` path parameter. sall (str): Search all ``sall`` path parameter. stag (str): Search tags ``stag`` path parameter. sgrp (str): Search groups ``sgrp`` path parameter. """ self._logger.debug('run: %s %s', request.method, request.uri) if scat: request.params['scat'] = scat else: request.params['scat'] = Const.CATEGORIES if sall: request.params['sall'] = sall if stag: request.params['stag'] = stag if sgrp: request.params['sgrp'] = sgrp api = Api(self._category, Api.UNIQUE, request.params) Config.load(api) self._content.run() if not self._content.uniques: Cause.push(Cause.HTTP_NOT_FOUND, 'cannot find unique fields for groups attribute') if Cause.is_ok(): response.content_type = ApiResource.MEDIA_JSON_API response.body = Generate.fields('groups', self._content.uniques, request, response) response.status = Cause.http_status() else: response.content_type = ApiResource.MEDIA_JSON_API response.body = Generate.error(Cause.json_message()) response.status = Cause.http_status() Cause.reset() self._logger.debug('end: %s %s', request.method, request.uri)
def delete(self): """Delete content.""" collection = self._storage.search( scat=Config.search_cat_kws, sall=Config.search_all_kws, stag=Config.search_tag_kws, sgrp=Config.search_grp_kws, search_filter=Config.search_filter, uuid=Config.operation_uuid, digest=Config.operation_digest, identity=Config.operation_identity, data=Config.content_data ) if len(collection) == 1: resource = next(collection.resources()) self._logger.debug('deleting: %s :with digest: %.16s', resource.category, resource.digest) self._storage.delete(resource.digest) else: Config.validate_search_context(collection, 'delete')