From c100a7dfde64de1c54e6760889bb608cae0bd321 Mon Sep 17 00:00:00 2001 From: Chris Elder Date: Wed, 17 Apr 2024 20:48:31 -0400 Subject: [PATCH] Add support for local MSP, channel config Signed-off-by: Chris Elder --- plugins/module_utils/msp_utils.py | 7 +- plugins/module_utils/utils.py | 21 ++ plugins/modules/channel_config.py | 62 +++- plugins/modules/channel_members.py | 177 ++++++++++++ .../membership_service_provider_local.py | 256 +++++++++++++++++ .../modules/ordering_service_node_metadata.py | 1 - plugins/modules/organization_list_info.py | 271 ++++++++++++++++++ 7 files changed, 791 insertions(+), 4 deletions(-) create mode 100644 plugins/modules/channel_members.py create mode 100644 plugins/modules/membership_service_provider_local.py create mode 100644 plugins/modules/organization_list_info.py diff --git a/plugins/module_utils/msp_utils.py b/plugins/module_utils/msp_utils.py index fd42d8e5..40f4859c 100644 --- a/plugins/module_utils/msp_utils.py +++ b/plugins/module_utils/msp_utils.py @@ -28,14 +28,17 @@ ''' -def convert_identity_to_msp_path(identity): +def convert_identity_to_msp_path(identity, path='temp'): # Ensure the identity has a CA, otherwise we cannot use it. if not identity.ca: raise Exception('The specified identity cannot be used as it does not have a CA field') # Create a temporary directory. - msp_path = tempfile.mkdtemp() + if path == 'temp': + msp_path = tempfile.mkdtemp() + else: + msp_path = path # Create the admin certificates directory (ideally would be empty, but # needs something in it to keep the CLI quiet). diff --git a/plugins/module_utils/utils.py b/plugins/module_utils/utils.py index 9e2557ce..2412d1cb 100644 --- a/plugins/module_utils/utils.py +++ b/plugins/module_utils/utils.py @@ -243,6 +243,27 @@ def get_all_orderering_service_nodes(console): return ordering_service_nodes +def get_all_organizations(console): + + # Go over each peer. + organizations = list() + + components = console.get_all_components_by_type('msp') + + if len(components) == 0: + return None + + for component in components: + + data = console.extract_organization_info(component) + + # Add the ordering service node. + organizations.append(Organization.from_json(data)) + + # Return the list of ordering service nodes. + return organizations + + def get_ordering_service_by_name(console, name, fail_on_missing=True): # Look up the ordering service by name. diff --git a/plugins/modules/channel_config.py b/plugins/modules/channel_config.py index 70c598ac..46f17fa4 100644 --- a/plugins/modules/channel_config.py +++ b/plugins/modules/channel_config.py @@ -17,6 +17,8 @@ from ansible.module_utils._text import to_native from ansible.module_utils.basic import _load_params, env_fallback +from pathlib import Path + from ..module_utils.dict_utils import diff_dicts from ..module_utils.fabric_utils import get_fabric_cfg_path from ..module_utils.file_utils import get_temp_file @@ -761,6 +763,60 @@ def sign_update(module): shutil.rmtree(fabric_cfg_path) +def sign_update_organizations(module): + + # Get the channel and target path. + path = module.params['path'] + + organizations_dir_param = module.params['organizations_dir'] + + organizations_dir = Path(organizations_dir_param).resolve() + + hsm = module.params['hsm'] + + # Load in the existing config update file and see if we've already signed it. + with open(path, 'rb') as file: + config_update_envelope_json = proto_to_json('common.Envelope', file.read()) + signatures = config_update_envelope_json['payload']['data'].get('signatures', list()) + + for msp_id in module.params['organizations']: + + for signature in signatures: + if msp_id == signature['signature_header']['creator']['mspid']: + continue + + # Need to sign it. + msp_path = os.path.join(organizations_dir, msp_id, "msp") + fabric_cfg_path = get_fabric_cfg_path() + + try: + env = os.environ.copy() + env['CORE_PEER_MSPCONFIGPATH'] = msp_path + env['CORE_PEER_LOCALMSPID'] = msp_id + env['FABRIC_CFG_PATH'] = fabric_cfg_path + module.json_log({ + 'CORE_PEER_MSPCONFIGPATH': msp_path, + 'CORE_PEER_LOCALMSPID': msp_id, + 'FABRIC_CFG_PATH': fabric_cfg_path + }) + if hsm: + env['CORE_PEER_BCCSP_DEFAULT'] = 'PKCS11' + env['CORE_PEER_BCCSP_PKCS11_LIBRARY'] = hsm['pkcs11library'] + env['CORE_PEER_BCCSP_PKCS11_LABEL'] = hsm['label'] + env['CORE_PEER_BCCSP_PKCS11_PIN'] = hsm['pin'] + env['CORE_PEER_BCCSP_PKCS11_HASH'] = 'SHA2' + env['CORE_PEER_BCCSP_PKCS11_SECURITY'] = '256' + env['CORE_PEER_BCCSP_PKCS11_FILEKEYSTORE_KEYSTORE'] = os.path.join(msp_path, 'keystore') + subprocess.run([ + 'peer', 'channel', 'signconfigtx', '-f', path + ], env=env, text=True, close_fds=True, check=True, capture_output=True) + + finally: + shutil.rmtree(fabric_cfg_path) + + module.exit_json(changed=True, path=path) + + def apply_update(module): # Log in to the console. @@ -801,7 +857,7 @@ def main(): api_secret=dict(type='str', no_log=True), api_timeout=dict(type='int', default=60), api_token_endpoint=dict(type='str', default='https://iam.cloud.ibm.com/identity/token'), - operation=dict(type='str', required=True, choices=['create', 'fetch', 'compute_update', 'sign_update', 'apply_update']), + operation=dict(type='str', required=True, choices=['create', 'fetch', 'compute_update', 'sign_update', 'sign_update_organizations', 'apply_update']), ordering_service=dict(type='raw'), ordering_service_nodes=dict(type='list', elements='raw'), tls_handshake_time_shift=dict(type='str', fallback=(env_fallback, ['IBP_TLS_HANDSHAKE_TIME_SHIFT'])), # TODO: Look into renaming this env variable @@ -817,6 +873,7 @@ def main(): original=dict(type='str'), updated=dict(type='str'), organizations=dict(type='list', elements='raw'), + organizations_dir=dict(type='str', default='organizations'), policies=dict(type='dict'), acls=dict(type='dict'), capabilities=dict(type='dict', default=dict(), options=dict( @@ -839,6 +896,7 @@ def main(): ('operation', 'fetch', ['api_endpoint', 'api_authtype', 'api_key', 'identity', 'msp_id', 'name', 'path']), ('operation', 'compute_update', ['name', 'path', 'original', 'updated']), ('operation', 'sign_update', ['identity', 'msp_id', 'name', 'path']), + ('operation', 'sign_update_organizations', ['organizations', 'organizations_dir', 'name', 'path']), ('operation', 'apply_update', ['api_endpoint', 'api_authtype', 'api_key', 'identity', 'msp_id', 'name', 'path']) ] # Ansible doesn't allow us to say "require one of X and Y only if condition A is true", @@ -867,6 +925,8 @@ def main(): compute_update(module) elif operation == 'sign_update': sign_update(module) + elif operation == 'sign_update_organizations': + sign_update_organizations(module) elif operation == 'apply_update': apply_update(module) else: diff --git a/plugins/modules/channel_members.py b/plugins/modules/channel_members.py new file mode 100644 index 00000000..5b350231 --- /dev/null +++ b/plugins/modules/channel_members.py @@ -0,0 +1,177 @@ +#!/usr/bin/python +# +# SPDX-License-Identifier: Apache-2.0 +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ..module_utils.module import BlockchainModule +from ..module_utils.proto_utils import proto_to_json, json_to_proto +from ..module_utils.dict_utils import copy_dict + +from ansible.module_utils._text import to_native + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: channel_members +short_description: Manage anchor peers for a channel +description: + - Manage anchor peers for the whole channel. + - Migrate anchor peer addresses to the newer open source format + - This module works with the IBM Support for Hyperledger Fabric software or the Hyperledger Fabric + Open Source Stack running in a Red Hat OpenShift or Kubernetes cluster. +author: Simon Stone (@sstone1) +options: + api_endpoint: + description: + - The URL for the Fabric operations console. + type: str + required: true + api_authtype: + description: + - C(basic) - Authenticate to the Fabric operations console using basic authentication. + You must provide both a valid API key using I(api_key) and API secret using I(api_secret). + type: str + required: true + api_key: + description: + - The API key for the Fabric operations console. + type: str + required: true + api_secret: + description: + - The API secret for the Fabric operations console. + - Only required when I(api_authtype) is C(basic). + type: str + api_timeout: + description: + - The timeout, in seconds, to use when interacting with the Fabric operations console. + type: int + default: 60 + path: + description: + - Path to current the channel configuration file. + - This file can be fetched by using the M(channel_config) module. + - This file will be updated in place. You will need to keep a copy of the original file for computing the configuration + update. + type: str + required: true + operation: + description: + - C(migrate_addresses_to_os) - Convert the anchor peer addresses in the channel to open source standards + type: str + required: true +notes: [] +requirements: [] +''' + +EXAMPLES = ''' +- name: Add the organization to the channel + hyperledger.fabric_ansible_collection.channel_member: + state: present + api_endpoint: https://console.example.org:32000 + api_authtype: basic + api_key: xxxxxxxx + api_secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + path: updated_config.bin + operation: 'migrate_addresses_to_os' +''' + +RETURN = ''' +--- +{} +''' + + +def migrate_addresses_to_os(module): + + changed = False + + # Get the organization and the target path. + path = module.params['path'] + + # Read the config. + with open(path, 'rb') as file: + config_json = proto_to_json('common.Config', file.read()) + + original_config_json = copy_dict(config_json) + + # check to see if this is a system channel + if 'Consortiums' in config_json['channel_group']['groups']: + module.exit_json(changed=changed, msps=None, original_config_json=None, updated_config_json=None) + + # Check to see if the channel member exists. + application_groups = config_json['channel_group']['groups']['Application']['groups'] + + organizations = list() + + for msp_id in application_groups: + + msp = config_json['channel_group']['groups']['Application']['groups'][msp_id] + + anchor_peers_value = msp['values']['AnchorPeers']['value']['anchor_peers'] + + for idx, anchor_peer in enumerate(anchor_peers_value): + + old_hostname = anchor_peer['host'].split(".") + + if not old_hostname[0].endswith('-peer'): + old_hostname[0] = old_hostname[0] + '-peer' + new_host_name = '.'.join(str(hostname_part) for hostname_part in old_hostname) + anchor_peers_value[idx]['host'] = new_host_name + changed = True + + if anchor_peer['port'] != 443: + anchor_peers_value[idx]['port'] = 443 + changed = True + + if changed: + organizations.append(msp_id) + + # Save the config. + config_proto = json_to_proto('common.Config', config_json) + with open(path, 'wb') as file: + file.write(config_proto) + module.exit_json(changed=changed, organizations=organizations, original_config_json=original_config_json, updated_config_json=config_json) + + +def main(): + + # Create the module. + argument_spec = dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + api_endpoint=dict(type='str', required=True), + api_authtype=dict(type='str', choices=['ibmcloud', 'basic'], required=True), + api_key=dict(type='str', no_log=True, required=True), + api_secret=dict(type='str', no_log=True), + api_timeout=dict(type='int', default=60), + api_token_endpoint=dict(type='str', default='https://iam.cloud.ibm.com/identity/token'), + path=dict(type='str', required=True), + operation=dict(type='str', required=True, choices=['migrate_addresses_to_os']) + ) + required_if = [ + ('api_authtype', 'basic', ['api_secret']), + ('operation', 'migrate_addresses_to_os', ['api_endpoint', 'api_authtype', 'api_key', 'path']) + ] + module = BlockchainModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if) + + # Ensure all exceptions are caught. + try: + operation = module.params['operation'] + if operation == 'migrate_addresses_to_os': + migrate_addresses_to_os(module) + else: + raise Exception(f'Invalid operation {operation}') + + # Notify Ansible of the exception. + except Exception as e: + module.fail_json(msg=to_native(e)) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/membership_service_provider_local.py b/plugins/modules/membership_service_provider_local.py new file mode 100644 index 00000000..ab1abf81 --- /dev/null +++ b/plugins/modules/membership_service_provider_local.py @@ -0,0 +1,256 @@ +#!/usr/bin/python +# +# SPDX-License-Identifier: Apache-2.0 +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ..module_utils.module import BlockchainModule +from ..module_utils.utils import get_console, get_all_organizations, resolve_identity +from ..module_utils.msp_utils import convert_identity_to_msp_path +from ..module_utils.enrolled_identities import EnrolledIdentity + +from cryptography import x509 +from cryptography.x509.oid import NameOID +from cryptography.hazmat.backends import default_backend + +from pathlib import Path +import json +import os +import base64 +import datetime +import tempfile +import shutil +import subprocess + +from ansible.module_utils._text import to_native + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: membership_service_provider_local +short_description: Construct a set of membership service provider directories +description: + - Gather information about all organizations + - Create a directory for each organization named by the msp_id. + - Create an msp folder for each msp to store the identity +author: Chris Elder +options: + api_endpoint: + description: + - The URL for the the Fabric operations console. + type: str + required: true + api_authtype: + description: + - C(basic) - Authenticate to the the Fabric operations console using basic authentication. + You must provide both a valid API key using I(api_key) and API secret using I(api_secret). + type: str + required: true + api_key: + description: + - The API key for the the Fabric operations console. + type: str + required: true + api_secret: + description: + - The API secret for the the Fabric operations console. + - Only required when I(api_authtype) is C(basic). + type: str + operation: + description: + - C(create) - Create an organizations directory with the MSPs for all organizations + type: str + required: true + organization_dir: + description: + - Directory used for creating local MSPs for all organizations. + - Default is organizations. + type: str + wallet: + description: + - Directory used for storing the admin certficates from the console wallet. + - Default is wallet. + type: str + wait_timeout: + description: + - The timeout, in seconds, to wait until the certificate authority is available. + type: int + default: 60 +notes: [] +requirements: [] +''' + +EXAMPLES = ''' +- name: Create the local msp for all organizations + hyperledger.fabric_ansible_collection.membership_service_provider_local: + api_endpoint: https://console.example.org:32000 + api_authtype: basic + api_key: xxxxxxxx + api_secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + operation: "create" +''' + +RETURN = ''' +--- +exists: + description: + - True if the process succeeds and creates the organizations directory. + returned: always + type: boolean +''' + + +def create(module): + + # Ensure all exceptions are caught. + try: + + # timeout = module.params['wait_timeout'] + + organizations_dir = module.params['organizations_dir'] + + if os.path.exists(organizations_dir): + shutil.rmtree(organizations_dir) + + wallet_dir = module.params['wallet_dir'] + + # Log in to the console. + console = get_console(module) + + organization_list = get_all_organizations(console) + + for organization in organization_list: + + Path(os.path.join(organizations_dir, organization.msp_id, 'msp')).mkdir(parents=True, exist_ok=True) + + with open(os.path.join(organizations_dir, organization.msp_id, organization.msp_id + '.json'), 'w', encoding='utf-8') as f: + json.dump(organization.to_json(), f, ensure_ascii=False, indent=4) + + # Scan the entries in the wallet directory + file_names = [fn for fn in os.listdir(wallet_dir) if fn.endswith('json')] + for identity_filename in file_names: + + identity_filename_path = os.path.join(wallet_dir, identity_filename) + + # Open the json for the identity + with open(identity_filename_path, 'r') as file: + identity = json.load(file) + + # decode the certificate + cert = x509.load_pem_x509_certificate(base64.b64decode(identity['cert']), default_backend()) + + # extract the nodeOU + ou = cert.subject.get_attributes_for_oid(NameOID.ORGANIZATIONAL_UNIT_NAME)[0].value + + if 'admin' not in ou: + module.json_log({ + 'msg': f'{identity_filename_path} is not an admin cert' + }) + continue + + if cert.not_valid_after < datetime.datetime.now(): + module.json_log({ + 'msg': f'{identity_filename_path} has expired' + }) + continue + + identity_found = False + + # if this cert matches the admin cert in the msp, then create the local msp and move to the next cert + for admin_cert in organization.admins: + if identity['cert'] == admin_cert: + create_local_msp(console, module, identity_filename_path, organizations_dir, identity, organization.msp_id) + identity_found = True + break + + if identity_found: + break + + try: + + # Create a temporary directory. + cert_path = tempfile.mkdtemp() + + certificate_file = os.path.join(cert_path, 'cert.pem') + + write_cert_to_file(certificate_file, identity['cert']) + + identity_found = False + for root_cert in organization.root_certs: + + ca_certificate_file = os.path.join(cert_path, 'cacert.pem') + write_cert_to_file(ca_certificate_file, root_cert) + + result = subprocess.run(['openssl', 'verify', '-CAfile', ca_certificate_file, certificate_file], capture_output=True, text=True) + + if result.returncode == 0: + create_local_msp(console, module, identity_filename, organizations_dir, identity, organization.msp_id) + identity_found = True + break + + if identity_found: + break + + finally: + shutil.rmtree(cert_path) + + module.exit_json(changed=True) + + # Notify Ansible of the exception. + except Exception as e: + module.fail_json(msg=to_native(e)) + + +def write_cert_to_file(filename, cert): + f = open(filename, "w") + f.write(base64.b64decode(cert).decode("ascii")) + f.close() + + +def create_local_msp(console, module, identity_filename_path, organizations_dir, identity, msp_id): + shutil.copyfile(identity_filename_path, os.path.join(organizations_dir, msp_id, 'identity.json')) + enrolled_identity = EnrolledIdentity.from_json(identity) + resolved_identity = resolve_identity(console, module, enrolled_identity, msp_id) + convert_identity_to_msp_path(resolved_identity, os.path.join(organizations_dir, msp_id, 'msp')) + + +def main(): + + # Create the module. + argument_spec = dict( + api_endpoint=dict(type='str', required=True), + api_authtype=dict(type='str', required=True, choices=['ibmcloud', 'basic']), + api_key=dict(type='str', required=True, no_log=True), + api_secret=dict(type='str', no_log=True), + api_timeout=dict(type='int', default=60), + api_token_endpoint=dict(type='str', default='https://iam.cloud.ibm.com/identity/token'), + operation=dict(type='str', required=True, choices=['create']), + organizations_dir=dict(type='str', default='organizations'), + wallet_dir=dict(type='str', default='wallet'), + wait_timeout=dict(type='int', default=60) + ) + required_if = [ + ('api_authtype', 'basic', ['api_secret']) + ] + module = BlockchainModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if) + + # Ensure all exceptions are caught. + try: + operation = module.params['operation'] + if operation == 'create': + create(module) + else: + raise Exception(f'Invalid operation {operation}') + + # Notify Ansible of the exception. + except Exception as e: + module.fail_json(msg=to_native(e)) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/ordering_service_node_metadata.py b/plugins/modules/ordering_service_node_metadata.py index 28438ac8..e572f2c5 100644 --- a/plugins/modules/ordering_service_node_metadata.py +++ b/plugins/modules/ordering_service_node_metadata.py @@ -79,7 +79,6 @@ api_secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx name: Ordering Service_1 preferred_url: os - type: tls_cert ''' RETURN = ''' diff --git a/plugins/modules/organization_list_info.py b/plugins/modules/organization_list_info.py new file mode 100644 index 00000000..79aed7f3 --- /dev/null +++ b/plugins/modules/organization_list_info.py @@ -0,0 +1,271 @@ +#!/usr/bin/python +# +# SPDX-License-Identifier: Apache-2.0 +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ..module_utils.module import BlockchainModule +from ..module_utils.utils import get_console, get_all_organizations + +from ansible.module_utils._text import to_native + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: organization_list_info +short_description: Get information about all Hyperledger Fabric organizations +description: + - Get information about all Hyperledger Fabric organizations. + - This module works with the IBM Support for Hyperledger Fabric software or the Hyperledger Fabric + Open Source Stack running in a Red Hat OpenShift or Kubernetes cluster. +author: Chris Elder +options: + api_endpoint: + description: + - The URL for the the Fabric operations console. + type: str + required: true + api_authtype: + description: + - C(basic) - Authenticate to the the Fabric operations console using basic authentication. + You must provide both a valid API key using I(api_key) and API secret using I(api_secret). + type: str + required: true + api_key: + description: + - The API key for the the Fabric operations console. + type: str + required: true + api_secret: + description: + - The API secret for the the Fabric operations console. + - Only required when I(api_authtype) is C(basic). + type: str + api_timeout: + description: + - The timeout, in seconds, to use when interacting with the the Fabric operations console. + type: int + default: 60 + wait_timeout: + description: + - The timeout, in seconds, to wait until the certificate authority is available. + type: int + default: 60 +notes: [] +requirements: [] +''' + +EXAMPLES = ''' +- name: Get all organizations + hyperledger.fabric_ansible_collection.organization_list_info: + api_endpoint: https://console.example.org:32000 + api_authtype: basic + api_key: xxxxxxxx + api_secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +''' + +RETURN = ''' +--- +exists: + description: + - True if the certificate authority exists, false otherwise. + returned: always + type: boolean +organization: + description: + - The organization. + returned: if organization exists + type: dict + contains: + name: + description: + - The name of the organization. + type: str + sample: Org1 + msp_id: + description: + - The MSP ID for the organization. + type: str + sample: Org1MSP + root_certs: + description: + - The list of root certificates for this organization. + - Root certificates must be supplied as base64 encoded PEM files. + type: list + elements: str + sample: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t... + intermediate_certs: + description: + - The list of intermediate certificates for this organization. + - Intermediate certificates must be supplied as base64 encoded PEM files. + type: list + elements: str + sample: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t... + admins: + description: + - The list of administrator certificates for this organization. + - Administrator certificates must be supplied as base64 encoded PEM files. + type: list + elements: str + sample: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t... + revocation_list: + description: + - The list of revoked certificates for this organization. + - Revoked certificates must be supplied as base64 encoded PEM files. + type: list + elements: str + sample: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t... + tls_root_certs: + description: + - The list of TLS root certificates for this organization. + - TLS root certificates must be supplied as base64 encoded PEM files. + type: list + elements: str + sample: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t... + tls_intermediate_certs: + description: + - The list of TLS root certificates for this organization. + - TLS intermediate certificates must be supplied as base64 encoded PEM files. + type: list + elements: str + sample: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t... + fabric_node_ous: + description: + - Configuration specific to the identity classification. + type: dict + contains: + enable: + description: + - True if identity classification is enabled for this organization, false otherwise. + type: boolean + sample: true + admin_ou_identifier: + description: + - Configuration specific to the admin identity classification. + type: dict + contains: + certificate: + description: + - The root or intermediate certificate for this identity classification. + - Root or intermediate certificates must be supplied as base64 encoded PEM files. + type: str + sample: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t... + organizational_unit_identifier: + description: + - The organizational unit (OU) identifier for this identity classification. + type: str + sample: admin + client_ou_identifier: + description: + - Configuration specific to the client identity classification. + type: dict + contains: + certificate: + description: + - The root or intermediate certificate for this identity classification. + - Root or intermediate certificates must be supplied as base64 encoded PEM files. + type: str + sample: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t... + organizational_unit_identifier: + description: + - The organizational unit (OU) identifier for this identity classification. + type: str + sample: client + peer_ou_identifier: + description: + - Configuration specific to the peer identity classification. + type: dict + contains: + certificate: + description: + - The root or intermediate certificate for this identity classification. + - Root or intermediate certificates must be supplied as base64 encoded PEM files. + type: str + sample: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t... + organizational_unit_identifier: + description: + - The organizational unit (OU) identifier for this identity classification. + type: str + sample: peer + orderer_ou_identifier: + description: + - Configuration specific to the orderer identity classification. + type: dict + contains: + certificate: + description: + - The root or intermediate certificate for this identity classification. + - Root or intermediate certificates must be supplied as base64 encoded PEM files. + type: str + sample: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t... + organizational_unit_identifier: + description: + - The organizational unit (OU) identifier for this identity classification. + type: str + sample: orderer + organizational_unit_identifiers: + description: + - The list of organizational unit identifiers for this organization. + type: list + elements: dict + contains: + certificate: + description: + - The root or intermediate certificate for this organizational unit identifier. + - Root or intermediate certificates must be supplied as base64 encoded PEM files. + type: str + sample: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t... + organizational_unit_identifier: + description: + - The organizational unit (OU) identifier. + type: str + sample: acctdept +''' + + +def main(): + + # Create the module. + argument_spec = dict( + api_endpoint=dict(type='str', required=True), + api_authtype=dict(type='str', required=True, choices=['ibmcloud', 'basic']), + api_key=dict(type='str', required=True, no_log=True), + api_secret=dict(type='str', no_log=True), + api_timeout=dict(type='int', default=60), + api_token_endpoint=dict(type='str', default='https://iam.cloud.ibm.com/identity/token'), + wait_timeout=dict(type='int', default=60) + ) + required_if = [ + ('api_authtype', 'basic', ['api_secret']) + ] + module = BlockchainModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if) + + # Ensure all exceptions are caught. + try: + + # Log in to the console. + console = get_console(module) + + # Determine if the certificate authority exists. + organization_list = get_all_organizations(console) + + organizations = list() + + for organization in organization_list: + organizations.append(organization.to_json()) + + # Return certificate authority information. + module.exit_json(exists=True, organizations=organizations) + + # Notify Ansible of the exception. + except Exception as e: + module.fail_json(msg=to_native(e)) + + +if __name__ == '__main__': + main()