def publish_collection(self, collection_path): """ Publishes a collection to a Galaxy server and returns the import task URI. :param collection_path: The path to the collection tarball to publish. :return: The import task URI that contains the import results. """ display.display("Publishing collection artifact '%s' to %s %s" % (collection_path, self.name, self.api_server)) b_collection_path = to_bytes(collection_path, errors='surrogate_or_strict') if not os.path.exists(b_collection_path): raise AnsibleError( "The collection path specified '%s' does not exist." % to_native(collection_path)) elif not tarfile.is_tarfile(b_collection_path): raise AnsibleError( "The collection path specified '%s' is not a tarball, use 'ansible-galaxy collection " "build' to create a proper release artifact." % to_native(collection_path)) with open(b_collection_path, 'rb') as collection_tar: sha256 = secure_hash_s(collection_tar.read(), hash_func=hashlib.sha256) content_type, b_form_data = prepare_multipart({ 'sha256': sha256, 'file': { 'filename': b_collection_path, 'mime_type': 'application/octet-stream', }, }) headers = { 'Content-type': content_type, 'Content-length': len(b_form_data), } if 'v3' in self.available_api_versions: n_url = _urljoin(self.api_server, self.available_api_versions['v3'], 'artifacts', 'collections') + '/' else: n_url = _urljoin(self.api_server, self.available_api_versions['v2'], 'collections') + '/' resp = self._call_galaxy( n_url, args=b_form_data, headers=headers, method='POST', auth_required=True, error_context_msg='Error when publishing collection to %s (%s)' % (self.name, self.api_server)) return resp['task']
def main(): argument_spec = url_argument_spec() argument_spec.update( dest=dict(type='path'), url_username=dict(type='str', aliases=['user']), url_password=dict(type='str', aliases=['password'], no_log=True), body=dict(type='raw'), body_format=dict(type='str', default='raw', choices=['form-urlencoded', 'json', 'raw', 'form-multipart']), src=dict(type='path'), method=dict(type='str', default='GET'), return_content=dict(type='bool', default=False), follow_redirects=dict(type='str', default='safe', choices=['all', 'no', 'none', 'safe', 'urllib2', 'yes']), creates=dict(type='path'), removes=dict(type='path'), status_code=dict(type='list', elements='int', default=[200]), timeout=dict(type='int', default=30), headers=dict(type='dict', default={}), unix_socket=dict(type='path'), remote_src=dict(type='bool', default=False), ca_path=dict(type='path', default=None), unredirected_headers=dict(type='list', elements='str', default=[]), decompress=dict(type='bool', default=True), ) module = AnsibleModule( argument_spec=argument_spec, add_file_common_args=True, mutually_exclusive=[['body', 'src']], ) url = module.params['url'] body = module.params['body'] body_format = module.params['body_format'].lower() method = module.params['method'].upper() dest = module.params['dest'] return_content = module.params['return_content'] creates = module.params['creates'] removes = module.params['removes'] status_code = [int(x) for x in list(module.params['status_code'])] socket_timeout = module.params['timeout'] ca_path = module.params['ca_path'] dict_headers = module.params['headers'] unredirected_headers = module.params['unredirected_headers'] decompress = module.params['decompress'] if not re.match('^[A-Z]+$', method): module.fail_json(msg="Parameter 'method' needs to be a single word in uppercase, like GET or POST.") if body_format == 'json': # Encode the body unless its a string, then assume it is pre-formatted JSON if not isinstance(body, string_types): body = json.dumps(body) if 'content-type' not in [header.lower() for header in dict_headers]: dict_headers['Content-Type'] = 'application/json' elif body_format == 'form-urlencoded': if not isinstance(body, string_types): try: body = form_urlencoded(body) except ValueError as e: module.fail_json(msg='failed to parse body as form_urlencoded: %s' % to_native(e), elapsed=0) if 'content-type' not in [header.lower() for header in dict_headers]: dict_headers['Content-Type'] = 'application/x-www-form-urlencoded' elif body_format == 'form-multipart': try: content_type, body = prepare_multipart(body) except (TypeError, ValueError) as e: module.fail_json(msg='failed to parse body as form-multipart: %s' % to_native(e)) dict_headers['Content-Type'] = content_type if creates is not None: # do not run the command if the line contains creates=filename # and the filename already exists. This allows idempotence # of uri executions. if os.path.exists(creates): module.exit_json(stdout="skipped, since '%s' exists" % creates, changed=False) if removes is not None: # do not run the command if the line contains removes=filename # and the filename does not exist. This allows idempotence # of uri executions. if not os.path.exists(removes): module.exit_json(stdout="skipped, since '%s' does not exist" % removes, changed=False) # Make the request start = datetime.datetime.utcnow() r, info = uri(module, url, dest, body, body_format, method, dict_headers, socket_timeout, ca_path, unredirected_headers, decompress) elapsed = (datetime.datetime.utcnow() - start).seconds if r and dest is not None and os.path.isdir(dest): filename = get_response_filename(r) or 'index.html' dest = os.path.join(dest, filename) if r and r.fp is not None: # r may be None for some errors # r.fp may be None depending on the error, which means there are no headers either content_type, main_type, sub_type, content_encoding = parse_content_type(r) else: content_type = 'application/octet-stream' main_type = 'aplication' sub_type = 'octet-stream' content_encoding = 'utf-8' maybe_json = content_type and any(candidate in sub_type for candidate in JSON_CANDIDATES) maybe_output = maybe_json or return_content or info['status'] not in status_code if maybe_output: try: if PY3 and (r.fp is None or r.closed): raise TypeError content = r.read() except (AttributeError, TypeError): # there was no content, but the error read() # may have been stored in the info as 'body' content = info.pop('body', b'') elif r: content = r else: content = None resp = {} resp['redirected'] = info['url'] != url resp.update(info) resp['elapsed'] = elapsed resp['status'] = int(resp['status']) resp['changed'] = False # Write the file out if requested if r and dest is not None: if resp['status'] in status_code and resp['status'] != 304: write_file(module, dest, content, resp) # allow file attribute changes resp['changed'] = True module.params['path'] = dest file_args = module.load_file_common_arguments(module.params, path=dest) resp['changed'] = module.set_fs_attributes_if_different(file_args, resp['changed']) resp['path'] = dest # Transmogrify the headers, replacing '-' with '_', since variables don't # work with dashes. # In python3, the headers are title cased. Lowercase them to be # compatible with the python2 behaviour. uresp = {} for key, value in iteritems(resp): ukey = key.replace("-", "_").lower() uresp[ukey] = value if 'location' in uresp: uresp['location'] = absolute_location(url, uresp['location']) # Default content_encoding to try if isinstance(content, binary_type): u_content = to_text(content, encoding=content_encoding) if maybe_json: try: js = json.loads(u_content) uresp['json'] = js except Exception: if PY2: sys.exc_clear() # Avoid false positive traceback in fail_json() on Python 2 else: u_content = None if module.no_log_values: uresp = sanitize_keys(uresp, module.no_log_values, NO_MODIFY_KEYS) if resp['status'] not in status_code: uresp['msg'] = 'Status code was %s and not %s: %s' % (resp['status'], status_code, uresp.get('msg', '')) if return_content: module.fail_json(content=u_content, **uresp) else: module.fail_json(**uresp) elif return_content: module.exit_json(content=u_content, **uresp) else: module.exit_json(**uresp)
def main(): argument_spec = url_argument_spec() argument_spec.update( dest=dict(type='path'), url_username=dict(type='str', aliases=['user']), url_password=dict(type='str', aliases=['password'], no_log=True), body=dict(type='raw'), body_format=dict( type='str', default='raw', choices=['form-urlencoded', 'json', 'raw', 'form-multipart']), src=dict(type='path'), method=dict(type='str', default='GET'), return_content=dict(type='bool', default=False), follow_redirects=dict( type='str', default='safe', choices=['all', 'no', 'none', 'safe', 'urllib2', 'yes']), creates=dict(type='path'), removes=dict(type='path'), status_code=dict(type='list', elements='int', default=[200]), timeout=dict(type='int', default=30), headers=dict(type='dict', default={}), unix_socket=dict(type='path'), remote_src=dict(type='bool', default=False), ca_path=dict(type='path', default=None), unredirected_headers=dict(type='list', elements='str', default=[]), ) module = AnsibleModule( argument_spec=argument_spec, add_file_common_args=True, mutually_exclusive=[['body', 'src']], ) url = module.params['url'] body = module.params['body'] body_format = module.params['body_format'].lower() method = module.params['method'].upper() dest = module.params['dest'] return_content = module.params['return_content'] creates = module.params['creates'] removes = module.params['removes'] status_code = [int(x) for x in list(module.params['status_code'])] socket_timeout = module.params['timeout'] ca_path = module.params['ca_path'] dict_headers = module.params['headers'] unredirected_headers = module.params['unredirected_headers'] if not re.match('^[A-Z]+$', method): module.fail_json( msg= "Parameter 'method' needs to be a single word in uppercase, like GET or POST." ) if body_format == 'json': # Encode the body unless its a string, then assume it is pre-formatted JSON if not isinstance(body, string_types): body = json.dumps(body) if 'content-type' not in [header.lower() for header in dict_headers]: dict_headers['Content-Type'] = 'application/json' elif body_format == 'form-urlencoded': if not isinstance(body, string_types): try: body = form_urlencoded(body) except ValueError as e: module.fail_json( msg='failed to parse body as form_urlencoded: %s' % to_native(e), elapsed=0) if 'content-type' not in [header.lower() for header in dict_headers]: dict_headers['Content-Type'] = 'application/x-www-form-urlencoded' elif body_format == 'form-multipart': try: content_type, body = prepare_multipart(body) except (TypeError, ValueError) as e: module.fail_json(msg='failed to parse body as form-multipart: %s' % to_native(e)) dict_headers['Content-Type'] = content_type if creates is not None: # do not run the command if the line contains creates=filename # and the filename already exists. This allows idempotence # of uri executions. if os.path.exists(creates): module.exit_json(stdout="skipped, since '%s' exists" % creates, changed=False) if removes is not None: # do not run the command if the line contains removes=filename # and the filename does not exist. This allows idempotence # of uri executions. if not os.path.exists(removes): module.exit_json(stdout="skipped, since '%s' does not exist" % removes, changed=False) # Make the request start = datetime.datetime.utcnow() resp, content, dest = uri(module, url, dest, body, body_format, method, dict_headers, socket_timeout, ca_path, unredirected_headers) resp['elapsed'] = (datetime.datetime.utcnow() - start).seconds resp['status'] = int(resp['status']) resp['changed'] = False # Write the file out if requested if dest is not None: if resp['status'] in status_code and resp['status'] != 304: write_file(module, url, dest, content, resp) # allow file attribute changes resp['changed'] = True module.params['path'] = dest file_args = module.load_file_common_arguments(module.params, path=dest) resp['changed'] = module.set_fs_attributes_if_different( file_args, resp['changed']) resp['path'] = dest # Transmogrify the headers, replacing '-' with '_', since variables don't # work with dashes. # In python3, the headers are title cased. Lowercase them to be # compatible with the python2 behaviour. uresp = {} for key, value in iteritems(resp): ukey = key.replace("-", "_").lower() uresp[ukey] = value if 'location' in uresp: uresp['location'] = absolute_location(url, uresp['location']) # Default content_encoding to try content_encoding = 'utf-8' if 'content_type' in uresp: # Handle multiple Content-Type headers charsets = [] content_types = [] for value in uresp['content_type'].split(','): ct, params = cgi.parse_header(value) if ct not in content_types: content_types.append(ct) if 'charset' in params: if params['charset'] not in charsets: charsets.append(params['charset']) if content_types: content_type = content_types[0] if len(content_types) > 1: module.warn( 'Received multiple conflicting Content-Type values (%s), using %s' % (', '.join(content_types), content_type)) if charsets: content_encoding = charsets[0] if len(charsets) > 1: module.warn( 'Received multiple conflicting charset values (%s), using %s' % (', '.join(charsets), content_encoding)) u_content = to_text(content, encoding=content_encoding) if any(candidate in content_type for candidate in JSON_CANDIDATES): try: js = json.loads(u_content) uresp['json'] = js except Exception: if PY2: sys.exc_clear( ) # Avoid false positive traceback in fail_json() on Python 2 else: u_content = to_text(content, encoding=content_encoding) if module.no_log_values: uresp = sanitize_keys(uresp, module.no_log_values, NO_MODIFY_KEYS) if resp['status'] not in status_code: uresp['msg'] = 'Status code was %s and not %s: %s' % ( resp['status'], status_code, uresp.get('msg', '')) if return_content: module.fail_json(content=u_content, **uresp) else: module.fail_json(**uresp) elif return_content: module.exit_json(content=u_content, **uresp) else: module.exit_json(**uresp)
def test_bad_mime(mocker): fields = {'foo': {'filename': 'foo.boom', 'content': 'foo'}} mocker.patch('mimetypes.guess_type', side_effect=TypeError) content_type, b_data = prepare_multipart(fields) assert b'Content-Type: application/octet-stream' in b_data
def test_unknown_mime(mocker): fields = {'foo': {'filename': 'foo.boom', 'content': 'foo'}} mocker.patch('mimetypes.guess_type', return_value=(None, None)) content_type, b_data = prepare_multipart(fields) assert b'Content-Type: application/octet-stream' in b_data
def test_prepare_multipart(): fixture_boundary = b'===============3996062709511591449==' here = os.path.dirname(__file__) multipart = os.path.join(here, 'fixtures/multipart.txt') client_cert = os.path.join(here, 'fixtures/client.pem') client_key = os.path.join(here, 'fixtures/client.key') client_txt = os.path.join(here, 'fixtures/client.txt') fields = { 'form_field_1': 'form_value_1', 'form_field_2': { 'content': 'form_value_2', }, 'form_field_3': { 'content': '<html></html>', 'mime_type': 'text/html', }, 'form_field_4': { 'content': '{"foo": "bar"}', 'mime_type': 'application/json', }, 'file1': { 'content': 'file_content_1', 'filename': 'fake_file1.txt', }, 'file2': { 'content': '<html></html>', 'mime_type': 'text/html', 'filename': 'fake_file2.html', }, 'file3': { 'content': '{"foo": "bar"}', 'mime_type': 'application/json', 'filename': 'fake_file3.json', }, 'file4': { 'filename': client_cert, 'mime_type': 'text/plain', }, 'file5': { 'filename': client_key, 'mime_type': 'application/octet-stream' }, 'file6': { 'filename': client_txt, }, } content_type, b_data = prepare_multipart(fields) headers = Message() headers['Content-Type'] = content_type assert headers.get_content_type() == 'multipart/form-data' boundary = headers.get_boundary() assert boundary is not None with open(multipart, 'rb') as f: b_expected = f.read().replace(fixture_boundary, boundary.encode()) # Depending on Python version, there may or may not be a trailing newline assert b_data.rstrip(b'\r\n') == b_expected.rstrip(b'\r\n')