def k8s_config_resource_name(resource): if not HAS_GENERATE_HASH: raise AnsibleFilterError("k8s_config_resource_name requires openshift>=0.6.3") try: return resource['metadata']['name'] + '-' + generate_hash(resource) except KeyError: raise AnsibleFilterError("resource must have a metadata.name key to generate a resource name")
def perform_action(self, resource, definition): delete_options = self.params.get('delete_options') result = {'changed': False, 'result': {}} state = self.params.get('state', None) force = self.params.get('force', False) name = definition['metadata'].get('name') namespace = definition['metadata'].get('namespace') existing = None wait = self.params.get('wait') wait_sleep = self.params.get('wait_sleep') wait_timeout = self.params.get('wait_timeout') wait_condition = None if self.params.get('wait_condition' ) and self.params['wait_condition'].get('type'): wait_condition = self.params['wait_condition'] self.remove_aliases() try: # ignore append_hash for resources other than ConfigMap and Secret if self.append_hash and definition['kind'] in [ 'ConfigMap', 'Secret' ]: name = '%s-%s' % (name, generate_hash(definition)) definition['metadata']['name'] = name params = dict(name=name) if namespace: params['namespace'] = namespace existing = resource.get(**params) except (NotFoundError, MethodNotAllowedError): # Remove traceback so that it doesn't show up in later failures try: sys.exc_clear() except AttributeError: # no sys.exc_clear on python3 pass except ForbiddenError as exc: if definition['kind'] in ['Project', 'ProjectRequest' ] and state != 'absent': return self.create_project_request(definition) self.fail_json( msg='Failed to retrieve requested object: {0}'.format( exc.body), error=exc.status, status=exc.status, reason=exc.reason) except DynamicApiError as exc: self.fail_json( msg='Failed to retrieve requested object: {0}'.format( exc.body), error=exc.status, status=exc.status, reason=exc.reason) except ValueError as value_exc: self.fail_json( msg='Failed to retrieve requested object: {0}'.format( to_native(value_exc)), error='', status='', reason='') if state == 'absent': result['method'] = "delete" if not existing: # The object already does not exist return result else: # Delete the object result['changed'] = True if not self.check_mode: if delete_options: body = { 'apiVersion': 'v1', 'kind': 'DeleteOptions', } body.update(delete_options) params['body'] = body try: k8s_obj = resource.delete(**params) result['result'] = k8s_obj.to_dict() except DynamicApiError as exc: self.fail_json( msg="Failed to delete object: {0}".format( exc.body), error=exc.status, status=exc.status, reason=exc.reason) if wait: success, resource, duration = self.wait( resource, definition, wait_sleep, wait_timeout, 'absent') result['duration'] = duration if not success: self.fail_json(msg="Resource deletion timed out", **result) return result else: if self.apply: if self.check_mode: ignored, patch = apply_object( resource, _encode_stringdata(definition)) if existing: k8s_obj = dict_merge(existing.to_dict(), patch) else: k8s_obj = patch else: try: k8s_obj = resource.apply( definition, namespace=namespace).to_dict() except DynamicApiError as exc: msg = "Failed to apply object: {0}".format(exc.body) if self.warnings: msg += "\n" + "\n ".join(self.warnings) self.fail_json(msg=msg, error=exc.status, status=exc.status, reason=exc.reason) success = True result['result'] = k8s_obj if wait and not self.check_mode: success, result['result'], result['duration'] = self.wait( resource, definition, wait_sleep, wait_timeout, condition=wait_condition) if existing: existing = existing.to_dict() else: existing = {} match, diffs = self.diff_objects(existing, result['result']) result['changed'] = not match result['diff'] = diffs result['method'] = 'apply' if not success: self.fail_json(msg="Resource apply timed out", **result) return result if not existing: if self.check_mode: k8s_obj = _encode_stringdata(definition) else: try: k8s_obj = resource.create( definition, namespace=namespace).to_dict() except ConflictError: # Some resources, like ProjectRequests, can't be created multiple times, # because the resources that they create don't match their kind # In this case we'll mark it as unchanged and warn the user self.warn( "{0} was not found, but creating it returned a 409 Conflict error. This can happen \ if the resource you are creating does not directly create a resource of the same kind." .format(name)) return result except DynamicApiError as exc: msg = "Failed to create object: {0}".format(exc.body) if self.warnings: msg += "\n" + "\n ".join(self.warnings) self.fail_json(msg=msg, error=exc.status, status=exc.status, reason=exc.reason) except Exception as exc: msg = "Failed to create object: {0}".format(exc) if self.warnings: msg += "\n" + "\n ".join(self.warnings) self.fail_json(msg=msg, error='', status='', reason='') success = True result['result'] = k8s_obj if wait and not self.check_mode: success, result['result'], result['duration'] = self.wait( resource, definition, wait_sleep, wait_timeout, condition=wait_condition) result['changed'] = True result['method'] = 'create' if not success: self.fail_json(msg="Resource creation timed out", **result) return result match = False diffs = [] if existing and force: if self.check_mode: k8s_obj = _encode_stringdata(definition) else: try: k8s_obj = resource.replace( definition, name=name, namespace=namespace, append_hash=self.append_hash).to_dict() except DynamicApiError as exc: msg = "Failed to replace object: {0}".format(exc.body) if self.warnings: msg += "\n" + "\n ".join(self.warnings) self.fail_json(msg=msg, error=exc.status, status=exc.status, reason=exc.reason) match, diffs = self.diff_objects(existing.to_dict(), k8s_obj) success = True result['result'] = k8s_obj if wait and not self.check_mode: success, result['result'], result['duration'] = self.wait( resource, definition, wait_sleep, wait_timeout, condition=wait_condition) match, diffs = self.diff_objects(existing.to_dict(), result['result']) result['changed'] = not match result['method'] = 'replace' result['diff'] = diffs if not success: self.fail_json(msg="Resource replacement timed out", **result) return result # Differences exist between the existing obj and requested params if self.check_mode: k8s_obj = dict_merge(existing.to_dict(), _encode_stringdata(definition)) else: if LooseVersion( self.openshift_version) < LooseVersion("0.6.2"): k8s_obj, error = self.patch_resource( resource, definition, existing, name, namespace) else: for merge_type in self.params['merge_type'] or [ 'strategic-merge', 'merge' ]: k8s_obj, error = self.patch_resource( resource, definition, existing, name, namespace, merge_type=merge_type) if not error: break if error: self.fail_json(**error) success = True result['result'] = k8s_obj if wait and not self.check_mode: success, result['result'], result['duration'] = self.wait( resource, definition, wait_sleep, wait_timeout, condition=wait_condition) match, diffs = self.diff_objects(existing.to_dict(), result['result']) result['changed'] = not match result['method'] = 'patch' result['diff'] = diffs if not success: self.fail_json(msg="Resource update timed out", **result) return result
def test_hashes(): for test in tests: assert (generate_hash(test['resource']) == test['expected'])
def k8s_config_hash(resource): if not HAS_GENERATE_HASH: raise AnsibleFilterError("k8s_config_hash requires openshift>=0.6.3") return generate_hash(resource)
def perform_action(self, resource, definition): result = {'changed': False, 'result': {}} state = self.params.get('state', None) force = self.params.get('force', False) name = definition['metadata'].get('name') namespace = definition['metadata'].get('namespace') existing = None self.remove_aliases() if definition['kind'].endswith('List'): result['result'] = resource.get(namespace=namespace).to_dict() result['changed'] = False result['method'] = 'get' return result try: # ignore append_hash for resources other than ConfigMap and Secret if self.append_hash and definition['kind'] in [ 'ConfigMap', 'Secret' ]: name = '%s-%s' % (name, generate_hash(definition)) definition['metadata']['name'] = name params = dict(name=name, namespace=namespace) existing = resource.get(**params) except NotFoundError: pass except ForbiddenError as exc: if definition['kind'] in ['Project', 'ProjectRequest' ] and state != 'absent': return self.create_project_request(definition) self.fail_json( msg='Failed to retrieve requested object: {0}'.format( exc.body), error=exc.status, status=exc.status, reason=exc.reason) except DynamicApiError as exc: self.fail_json( msg='Failed to retrieve requested object: {0}'.format( exc.body), error=exc.status, status=exc.status, reason=exc.reason) if state == 'absent': result['method'] = "delete" if not existing: # The object already does not exist return result else: # Delete the object if not self.check_mode: try: k8s_obj = resource.delete(**params) result['result'] = k8s_obj.to_dict() except DynamicApiError as exc: self.fail_json( msg="Failed to delete object: {0}".format( exc.body), error=exc.status, status=exc.status, reason=exc.reason) result['changed'] = True return result else: if not existing: if self.check_mode: k8s_obj = definition else: try: k8s_obj = resource.create( definition, namespace=namespace).to_dict() except ConflictError: # Some resources, like ProjectRequests, can't be created multiple times, # because the resources that they create don't match their kind # In this case we'll mark it as unchanged and warn the user self.warn( "{0} was not found, but creating it returned a 409 Conflict error. This can happen \ if the resource you are creating does not directly create a resource of the same kind." .format(name)) return result except DynamicApiError as exc: self.fail_json( msg="Failed to create object: {0}".format( exc.body), error=exc.status, status=exc.status, reason=exc.reason) result['result'] = k8s_obj result['changed'] = True result['method'] = 'create' return result match = False diffs = [] if existing and force: if self.check_mode: k8s_obj = definition else: try: k8s_obj = resource.replace( definition, name=name, namespace=namespace, append_hash=self.append_hash).to_dict() except DynamicApiError as exc: self.fail_json( msg="Failed to replace object: {0}".format( exc.body), error=exc.status, status=exc.status, reason=exc.reason) match, diffs = self.diff_objects(existing.to_dict(), k8s_obj) result['result'] = k8s_obj result['changed'] = not match result['method'] = 'replace' result['diff'] = diffs return result # Differences exist between the existing obj and requested params if self.check_mode: k8s_obj = dict_merge(existing.to_dict(), definition) else: if LooseVersion( self.openshift_version) < LooseVersion("0.6.2"): k8s_obj, error = self.patch_resource( resource, definition, existing, name, namespace) else: for merge_type in self.params['merge_type'] or [ 'strategic-merge', 'merge' ]: k8s_obj, error = self.patch_resource( resource, definition, existing, name, namespace, merge_type=merge_type) if not error: break if error: self.fail_json(**error) match, diffs = self.diff_objects(existing.to_dict(), k8s_obj) result['result'] = k8s_obj result['changed'] = not match result['method'] = 'patch' result['diff'] = diffs return result