diff --git a/tasks/manage-contract.yml b/tasks/manage-contract.yml index 917d19d..96e3dca 100644 --- a/tasks/manage-contract.yml +++ b/tasks/manage-contract.yml @@ -2,7 +2,28 @@ # SPDX-License-Identifier: Apache-2.0 # --- -- name: Install and instantiate contract on all channels +- name: Extract label from package + shell: > + set -o pipefail && + tar xOf {{ contract.package }} metadata.json + register: package_metadata + changed_when: False + +- name: Set package label + set_fact: + package_label: "{{ (package_metadata.stdout | from_json).label }}" + +- name: Determine SHA256 hash of package + stat: + path: "{{ contract.package }}" + checksum_algorithm: sha256 + register: package_stats + +- name: Set package ID + set_fact: + package_id: "{{ package_label }}:{{ package_stats.stat.checksum }}" + +- name: Install, approve and commit all definitions on all channels include_tasks: manage-contract/channel.yml with_items: "{{ contract.channels }}" loop_control: diff --git a/tasks/manage-contract/approve-contract-member.yml b/tasks/manage-contract/approve-contract-member.yml new file mode 100644 index 0000000..bbaa0d8 --- /dev/null +++ b/tasks/manage-contract/approve-contract-member.yml @@ -0,0 +1,27 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# +--- +- name: Select peer to use for approve operations + set_fact: + peer: "{{ member.endorsing_peers[0] }}" + +- name: Approve contract + command: > + peer lifecycle chaincode approveformyorg + -C {{ channel.name }} + -n {{ definition.name }} + -v {{ definition.version }} + --package-id {{ package_id }} + --sequence {{ sequence_number }} + -o {{ ibp[channel.orderer.id].hostname }}:{{ ibp[channel.orderer.id].port }} + {{ '--tls' if ibp[channel.orderer.id].protocol == 'grpcs' else '' }} + {{ '--cafile "' + ibp[channel.orderer.id].pem + '"' if ibp[channel.orderer.id].protocol == 'grpcs' else '' }} + {{ '--ordererTLSHostnameOverride ' + ibp[channel.orderer.id].internal_hostname if ibp[channel.orderer.id].internal_hostname is defined else '' }} + environment: + CORE_PEER_ADDRESS: "{{ ibp[peer.id].hostname }}:{{ ibp[peer.id].port }}" + CORE_PEER_MSPCONFIGPATH: "{{ member.wallet }}/{{ member.msp.admin.identity }}" + CORE_PEER_LOCALMSPID: "{{ member.msp.id }}" + CORE_PEER_TLS_ENABLED: "{{ 'true' if peer.tls.enabled else 'false' }}" + CORE_PEER_TLS_ROOTCERT_FILE: "{{ ibp[peer.id].pem if ibp[peer.id].pem is defined }}" + changed_when: True \ No newline at end of file diff --git a/tasks/manage-contract/approve-contract.yml b/tasks/manage-contract/approve-contract.yml new file mode 100644 index 0000000..3721818 --- /dev/null +++ b/tasks/manage-contract/approve-contract.yml @@ -0,0 +1,10 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# +--- +- name: Approve contract for all members in channel + include_tasks: approve-contract-member.yml + when: member.endorsing_peers is defined + with_items: "{{ definition.endorsing_members }}" + loop_control: + loop_var: member \ No newline at end of file diff --git a/tasks/manage-contract/calculate-sequence-number.yml b/tasks/manage-contract/calculate-sequence-number.yml new file mode 100644 index 0000000..b4cee29 --- /dev/null +++ b/tasks/manage-contract/calculate-sequence-number.yml @@ -0,0 +1,58 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# +--- +- name: Select member to use for query operations + set_fact: + member: "{{ definition.endorsing_members[0] }}" + +- name: Select peer to use for query operations + set_fact: + peer: "{{ member.endorsing_peers[0] }}" + +- name: Retrieve current committed contract list + command: > + peer lifecycle chaincode querycommitted + -C {{ channel.name }} + -n {{ definition.name }} + -O json + environment: + CORE_PEER_ADDRESS: "{{ ibp[peer.id].hostname }}:{{ ibp[peer.id].port }}" + CORE_PEER_MSPCONFIGPATH: "{{ member.wallet }}/{{ member.msp.admin.identity }}" + CORE_PEER_LOCALMSPID: "{{ member.msp.id }}" + CORE_PEER_TLS_ENABLED: "{{ 'true' if peer.tls.enabled else 'false' }}" + CORE_PEER_TLS_ROOTCERT_FILE: "{{ ibp[peer.id].pem if ibp[peer.id].pem is defined }}" + register: querycommitted + failed_when: False + changed_when: False + +- name: Load existing chaincode definition + set_fact: + committed_chaincode: "{{ querycommitted.stdout | from_json }}" + new_sequence_number_reqd: False + when: querycommitted.rc == 0 + +- name: Handle new chaincode definition + set_fact: + new_sequence_number_reqd: True + when: querycommitted.rc != 0 + +- name: Load existing sequence number + set_fact: + existing_sequence_number: "{{ (committed_chaincode | default({ 'sequence': 0 })).sequence }}" + when: not new_sequence_number_reqd + +- name: Determine if version has changed + set_fact: + new_sequence_number_reqd: True + when: not new_sequence_number_reqd and committed_chaincode.version != definition.version + +- name: Set next sequence number + set_fact: + sequence_number: "{{ existing_sequence_number + 1 }}" + when: new_sequence_number_reqd + +- name: Use existing sequence number + set_fact: + sequence_number: "{{ existing_sequence_number }}" + when: not new_sequence_number_reqd \ No newline at end of file diff --git a/tasks/manage-contract/channel.yml b/tasks/manage-contract/channel.yml index 2b8a349..28b7c37 100644 --- a/tasks/manage-contract/channel.yml +++ b/tasks/manage-contract/channel.yml @@ -2,8 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 # --- -- name: Install contract on all members peers in the channel - include_tasks: install-contract.yml - -- name: Instantiate contract on channel - include_tasks: instantiate-contract.yml \ No newline at end of file +- name: Install, approve and commit all definitions + include_tasks: manage-contract/definition.yml + with_items: "{{ channel.definitions }}" + loop_control: + loop_var: definition \ No newline at end of file diff --git a/tasks/manage-contract/commit-contract.yml b/tasks/manage-contract/commit-contract.yml new file mode 100644 index 0000000..1f03065 --- /dev/null +++ b/tasks/manage-contract/commit-contract.yml @@ -0,0 +1,30 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# +--- +- name: Select member to use for commit operation + set_fact: + member: "{{ definition.endorsing_members[0] }}" + +- name: Select peer to use for commit operation + set_fact: + peer: "{{ member.endorsing_peers[0] }}" + +- name: Commit contract + command: > + peer lifecycle chaincode commit + -C {{ channel.name }} + -n {{ definition.name }} + -v {{ definition.version }} + --sequence {{ sequence_number }} + -o {{ ibp[channel.orderer.id].hostname }}:{{ ibp[channel.orderer.id].port }} + {{ '--tls' if ibp[channel.orderer.id].protocol == 'grpcs' else '' }} + {{ '--cafile "' + ibp[channel.orderer.id].pem + '"' if ibp[channel.orderer.id].protocol == 'grpcs' else '' }} + {{ '--ordererTLSHostnameOverride ' + ibp[channel.orderer.id].internal_hostname if ibp[channel.orderer.id].internal_hostname is defined else '' }} + environment: + CORE_PEER_ADDRESS: "{{ ibp[peer.id].hostname }}:{{ ibp[peer.id].port }}" + CORE_PEER_MSPCONFIGPATH: "{{ member.wallet }}/{{ member.msp.admin.identity }}" + CORE_PEER_LOCALMSPID: "{{ member.msp.id }}" + CORE_PEER_TLS_ENABLED: "{{ 'true' if peer.tls.enabled else 'false' }}" + CORE_PEER_TLS_ROOTCERT_FILE: "{{ ibp[peer.id].pem if ibp[peer.id].pem is defined }}" + changed_when: True \ No newline at end of file diff --git a/tasks/manage-contract/definition.yml b/tasks/manage-contract/definition.yml new file mode 100644 index 0000000..95a7e56 --- /dev/null +++ b/tasks/manage-contract/definition.yml @@ -0,0 +1,16 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# +--- +- name: Install contract on all members peers in the channel + include_tasks: install-contract.yml + +- name: Calculate sequence number + include_tasks: calculate-sequence-number.yml + +- name: Approve contract for all members in the channel + include_tasks: approve-contract.yml + +- name: Commit contract in the channel + include_tasks: commit-contract.yml + when: new_sequence_number_reqd \ No newline at end of file diff --git a/tasks/manage-contract/install-contract-peer.yml b/tasks/manage-contract/install-contract-peer.yml index 6456d7e..c65267e 100644 --- a/tasks/manage-contract/install-contract-peer.yml +++ b/tasks/manage-contract/install-contract-peer.yml @@ -3,27 +3,32 @@ # --- - name: Retrieve current installed contract list - command: peer chaincode list --installed + command: peer lifecycle chaincode queryinstalled -O json environment: CORE_PEER_ADDRESS: "{{ ibp[peer.id].hostname }}:{{ ibp[peer.id].port }}" CORE_PEER_MSPCONFIGPATH: "{{ member.wallet }}/{{ member.msp.admin.identity }}" CORE_PEER_LOCALMSPID: "{{ member.msp.id }}" CORE_PEER_TLS_ENABLED: "{{ 'true' if peer.tls.enabled else 'false' }}" CORE_PEER_TLS_ROOTCERT_FILE: "{{ ibp[peer.id].pem if ibp[peer.id].pem is defined }}" - register: chaincode_list + register: queryinstalled failed_when: False changed_when: False - name: Determine if peer has contract installed set_fact: - contract_installed: "{{ chaincode_list.stdout | join('') is search('Name: ' ~ contract.name ~ ', Version: ' ~ contract.version ~ ',') }}" + installed_chaincodes: "{{ + (queryinstalled.stdout | from_json).installed_chaincodes | + default([]) | + selectattr('package_id', 'equalto', package_id) | + list + }}" - name: Install contract on peer - command: peer chaincode install "{{ contract.package }}" + command: peer lifecycle chaincode install "{{ contract.package }}" environment: CORE_PEER_ADDRESS: "{{ ibp[peer.id].hostname }}:{{ ibp[peer.id].port }}" CORE_PEER_MSPCONFIGPATH: "{{ member.wallet }}/{{ member.msp.admin.identity }}" CORE_PEER_LOCALMSPID: "{{ member.msp.id }}" CORE_PEER_TLS_ENABLED: "{{ 'true' if peer.tls.enabled else 'false' }}" CORE_PEER_TLS_ROOTCERT_FILE: "{{ ibp[peer.id].pem if ibp[peer.id].pem is defined }}" - when: not contract_installed \ No newline at end of file + when: not installed_chaincodes \ No newline at end of file diff --git a/tasks/manage-contract/install-contract.yml b/tasks/manage-contract/install-contract.yml index bedbe49..59a5ae4 100644 --- a/tasks/manage-contract/install-contract.yml +++ b/tasks/manage-contract/install-contract.yml @@ -5,6 +5,6 @@ - name: Install contract on all members peers in channel include_tasks: install-contract-member.yml when: member.endorsing_peers is defined - with_items: "{{ channel.endorsing_members }}" + with_items: "{{ definition.endorsing_members }}" loop_control: loop_var: member \ No newline at end of file diff --git a/tasks/manage-contract/instantiate-contract.yml b/tasks/manage-contract/instantiate-contract.yml deleted file mode 100644 index 3c48ab7..0000000 --- a/tasks/manage-contract/instantiate-contract.yml +++ /dev/null @@ -1,75 +0,0 @@ -# -# SPDX-License-Identifier: Apache-2.0 -# ---- -- name: Select member to use for instantiate operations - set_fact: - member: "{{ channel.endorsing_members[0] }}" - -- name: Select peer to use for instantiate operations - set_fact: - peer: "{{ member.endorsing_peers[0] }}" - -- name: Select orderer to use for instantiate operations - set_fact: - orderer: "{{ channel.orderer }}" - -- name: Retrieve current instantiated contract list - command: peer chaincode list --instantiated -C {{ channel.name }} - environment: - CORE_PEER_ADDRESS: "{{ ibp[peer.id].hostname }}:{{ ibp[peer.id].port }}" - CORE_PEER_MSPCONFIGPATH: "{{ member.wallet }}/{{ member.msp.admin.identity }}" - CORE_PEER_LOCALMSPID: "{{ member.msp.id }}" - CORE_PEER_TLS_ENABLED: "{{ 'true' if peer.tls.enabled else 'false' }}" - CORE_PEER_TLS_ROOTCERT_FILE: "{{ ibp[peer.id].pem if ibp[peer.id].pem is defined }}" - register: chaincode_list - failed_when: False - changed_when: False - -- name: Determine if channel has contract instantiated at correct version - set_fact: - contract_instantiated: "{{ chaincode_list.stdout | join('') is search('Name: ' ~ contract.name ~ ', Version: ' ~ contract.version ~ ',') }}" - -- name: Determine if channel has contract instantiated at any version - set_fact: - contract_instantiated_anyversion: "{{ chaincode_list.stdout | join('') is search('Name: ' ~ contract.name ~ ',') }}" - -- name: Instantiate contract on channel - command: > - peer chaincode instantiate - -C {{ channel.name }} - -c '{"Args":[]}' - -n {{ contract.name }} - -v {{ contract.version }} - -P {{ channel.endorsement_policy | quote }} - -o {{ ibp[channel.orderer.id].hostname }}:{{ ibp[channel.orderer.id].port }} - {{ '--tls' if ibp[channel.orderer.id].protocol == 'grpcs' else '' }} - {{ '--cafile "' + ibp[channel.orderer.id].pem + '"' if ibp[channel.orderer.id].protocol == 'grpcs' else '' }} - {{ '--ordererTLSHostnameOverride ' + ibp[channel.orderer.id].internal_hostname if ibp[channel.orderer.id].internal_hostname is defined else '' }} - environment: - CORE_PEER_ADDRESS: "{{ ibp[peer.id].hostname }}:{{ ibp[peer.id].port }}" - CORE_PEER_MSPCONFIGPATH: "{{ member.wallet }}/{{ member.msp.admin.identity }}" - CORE_PEER_LOCALMSPID: "{{ member.msp.id }}" - CORE_PEER_TLS_ENABLED: "{{ 'true' if peer.tls.enabled else 'false' }}" - CORE_PEER_TLS_ROOTCERT_FILE: "{{ ibp[peer.id].pem if ibp[peer.id].pem is defined }}" - when: (not contract_instantiated) and (not contract_instantiated_anyversion) - -- name: Upgrade contract on channel - command: > - peer chaincode upgrade - -C {{ channel.name }} - -c '{"Args":[]}' - -n {{ contract.name }} - -v {{ contract.version }} - -P {{ channel.endorsement_policy | quote }} - -o {{ ibp[channel.orderer.id].hostname }}:{{ ibp[channel.orderer.id].port }} - {{ '--tls' if ibp[channel.orderer.id].protocol == 'grpcs' else '' }} - {{ '--cafile "' + ibp[channel.orderer.id].pem + '"' if ibp[channel.orderer.id].protocol == 'grpcs' else '' }} - {{ '--ordererTLSHostnameOverride ' + ibp[channel.orderer.id].internal_hostname if ibp[channel.orderer.id].internal_hostname is defined else '' }} - environment: - CORE_PEER_ADDRESS: "{{ ibp[peer.id].hostname }}:{{ ibp[peer.id].port }}" - CORE_PEER_MSPCONFIGPATH: "{{ member.wallet }}/{{ member.msp.admin.identity }}" - CORE_PEER_LOCALMSPID: "{{ member.msp.id }}" - CORE_PEER_TLS_ENABLED: "{{ 'true' if peer.tls.enabled else 'false' }}" - CORE_PEER_TLS_ROOTCERT_FILE: "{{ ibp[peer.id].pem if ibp[peer.id].pem is defined }}" - when: (not contract_instantiated) and contract_instantiated_anyversion diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..d0b8cc3 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,9 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +gateways +nodes +wallets +!**/.gitkeep +service-creds.json \ No newline at end of file diff --git a/tests/fabcar@1.0.0.cds b/tests/fabcar@1.0.0.cds deleted file mode 100644 index 3439a76..0000000 Binary files a/tests/fabcar@1.0.0.cds and /dev/null differ diff --git a/tests/fabcar@1.0.0.tgz b/tests/fabcar@1.0.0.tgz new file mode 100644 index 0000000..3d54ce9 Binary files /dev/null and b/tests/fabcar@1.0.0.tgz differ diff --git a/tests/test.yml b/tests/test.yml index 7ff4cbb..85bcb30 100644 --- a/tests/test.yml +++ b/tests/test.yml @@ -190,20 +190,21 @@ - *Org2Peer2 anchor_peers: - *Org2Peer1 - # contracts: - # - name: fabcar - # version: 1.0.0 - # package: "{{ playbook_dir }}/fabcar@1.0.0.cds" - # channels: - # - <<: *Channel1 - # endorsement_policy: "AND('Org1MSP.peer','Org2MSP.peer')" - # endorsing_members: - # - <<: *Org1 - # endorsing_peers: - # - <<: *Org1Peer1 - # - <<: *Org2 - # endorsing_peers: - # - <<: *Org2Peer1 + contracts: + - package: "{{ playbook_dir }}/fabcar@1.0.0.tgz" + channels: + - <<: *Channel1 + definitions: + - name: fabcar + version: 1.0.0 + endorsement_policy: "AND('Org1MSP.peer','Org2MSP.peer')" + endorsing_members: + - <<: *Org1 + endorsing_peers: + - <<: *Org1Peer1 + - <<: *Org2 + endorsing_peers: + - <<: *Org2Peer1 gateways: - name: Org1 gateway organization: